this repo has no description

base

hailey.at 3f5828ba

+40
Makefile
···
+
SHELL = /bin/bash
+
.SHELLFLAGS = -o pipefail -c
+
+
.PHONY: help
+
help: ## Print info about all commands
+
@echo "Commands:"
+
@echo
+
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[01;32m%-20s\033[0m %s\n", $$1, $$2}'
+
+
.PHONY: build
+
build: ## Build all executables
+
go build -o oauth .
+
+
.PHONY: all
+
all: build
+
+
.PHONY: test
+
test: ## Run tests
+
go test ./...
+
+
.PHONY: coverage-html
+
coverage-html: ## Generate test coverage report and open in browser
+
go test ./... -coverpkg=./... -coverprofile=test-coverage.out
+
go tool cover -html=test-coverage.out
+
+
.PHONY: lint
+
lint: ## Verify code style and run static checks
+
go vet ./...
+
test -z $(gofmt -l ./...)
+
+
.PHONY: fmt
+
fmt: ## Run syntax re-formatting (modify in place)
+
go fmt ./...
+
+
.PHONY: check
+
check: ## Compile everything, checking syntax (does not output binaries)
+
go build ./...
+
+
.env:
+
if [ ! -f ".env" ]; then cp example.dev.env .env; fi
+39
go.mod
···
+
module github.com/haileyok/atproto-oauth-golang
+
+
go 1.24.0
+
+
require (
+
github.com/lestrrat-go/jwx/v2 v2.0.12
+
github.com/stretchr/testify v1.10.0
+
)
+
+
require (
+
github.com/bluesky-social/indigo v0.0.0-20250222003125-2503553ea604 // indirect
+
github.com/davecgh/go-spew v1.1.1 // indirect
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
+
github.com/goccy/go-json v0.10.2 // indirect
+
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
+
github.com/gorilla/context v1.1.2 // indirect
+
github.com/gorilla/securecookie v1.1.2 // indirect
+
github.com/gorilla/sessions v1.4.0 // indirect
+
github.com/labstack/echo-contrib v0.17.2 // indirect
+
github.com/labstack/echo/v4 v4.13.3 // indirect
+
github.com/labstack/gommon v0.4.2 // indirect
+
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
+
github.com/lestrrat-go/httpcc v1.0.1 // indirect
+
github.com/lestrrat-go/httprc v1.0.4 // indirect
+
github.com/lestrrat-go/iter v1.0.2 // indirect
+
github.com/lestrrat-go/option v1.0.1 // indirect
+
github.com/mattn/go-colorable v0.1.13 // indirect
+
github.com/mattn/go-isatty v0.0.20 // indirect
+
github.com/pmezard/go-difflib v1.0.0 // indirect
+
github.com/segmentio/asm v1.2.0 // indirect
+
github.com/valyala/bytebufferpool v1.0.0 // indirect
+
github.com/valyala/fasttemplate v1.2.2 // indirect
+
golang.org/x/crypto v0.31.0 // indirect
+
golang.org/x/net v0.33.0 // indirect
+
golang.org/x/sys v0.28.0 // indirect
+
golang.org/x/text v0.21.0 // indirect
+
golang.org/x/time v0.8.0 // indirect
+
gopkg.in/yaml.v3 v3.0.1 // indirect
+
)
+115
go.sum
···
+
github.com/bluesky-social/indigo v0.0.0-20250222003125-2503553ea604 h1:rceaPCufVEkobTmyISJhvY4kzaPKtSujN427CGWpvHw=
+
github.com/bluesky-social/indigo v0.0.0-20250222003125-2503553ea604/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA=
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
+
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
+
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
+
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
+
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
+
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
+
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
+
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
+
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
+
github.com/labstack/echo-contrib v0.17.2 h1:K1zivqmtcC70X9VdBFdLomjPDEVHlrcAObqmuFj1c6w=
+
github.com/labstack/echo-contrib v0.17.2/go.mod h1:NeDh3PX7j/u+jR4iuDt1zHmWZSCz9c/p9mxXcDpyS8E=
+
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
+
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
+
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
+
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
+
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
+
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
+
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
+
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
+
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
+
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
+
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
+
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
+
github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA=
+
github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ=
+
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
+
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
+
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
+
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
+
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
+
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
+
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
+
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+11
helpers.go
···
+
package oauth
+
+
func tokenInSet(tok string, set []string) bool {
+
for _, setTok := range set {
+
if tok == setTok {
+
return true
+
}
+
}
+
+
return false
+
}
+128
oauth.go
···
+
package oauth
+
+
import (
+
"context"
+
"fmt"
+
"io"
+
"net/http"
+
"net/url"
+
"time"
+
)
+
+
type OauthClient struct {
+
h *http.Client
+
}
+
+
type OauthClientArgs struct {
+
h *http.Client
+
}
+
+
func NewOauthClient(args OauthClientArgs) *OauthClient {
+
if args.h == nil {
+
args.h = &http.Client{
+
Timeout: 5 * time.Second,
+
}
+
}
+
return &OauthClient{
+
h: args.h,
+
}
+
}
+
+
func (o *OauthClient) ResolvePDSAuthServer(ctx context.Context, ustr string) (string, error) {
+
u, err := isSafeAndParsed(ustr)
+
if err != nil {
+
return "", err
+
}
+
+
u.Path = "/.well-known/oauth-protected-resource"
+
+
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
+
if err != nil {
+
return "", fmt.Errorf("error creating request for oauth protected resource: %w", err)
+
}
+
+
resp, err := o.h.Do(req)
+
if err != nil {
+
return "", fmt.Errorf("could not get response from server: %w", err)
+
}
+
defer resp.Body.Close()
+
+
if resp.StatusCode != http.StatusOK {
+
io.Copy(io.Discard, resp.Body)
+
return "", fmt.Errorf("received non-200 response from pds. code was %d", resp.StatusCode)
+
}
+
+
b, err := io.ReadAll(resp.Body)
+
if err != nil {
+
return "", fmt.Errorf("could not read body: %w", err)
+
}
+
+
var resource OauthProtectedResource
+
if err := resource.UnmarshalJSON(b); err != nil {
+
return "", fmt.Errorf("could not unmarshal json: %w", err)
+
}
+
+
if len(resource.AuthorizationServers) == 0 {
+
return "", fmt.Errorf("oauth protected resource contained no authorization servers")
+
}
+
+
return resource.AuthorizationServers[0], nil
+
}
+
+
func (o *OauthClient) FetchAuthServerMetadata(ctx context.Context, ustr string) (any, error) {
+
u, err := isSafeAndParsed(ustr)
+
if err != nil {
+
return nil, err
+
}
+
+
u.Path = "/.well-known/oauth-authorization-server"
+
+
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
+
if err != nil {
+
return nil, fmt.Errorf("error creating request to fetch auth metadata: %w", err)
+
}
+
+
resp, err := o.h.Do(req)
+
if err != nil {
+
return nil, fmt.Errorf("error getting response for auth metadata: %w", err)
+
}
+
defer resp.Body.Close()
+
+
if resp.StatusCode != http.StatusOK {
+
io.Copy(io.Discard, resp.Body)
+
return nil, fmt.Errorf("received non-200 response from pds. status code was %d", resp.StatusCode)
+
}
+
+
b, err := io.ReadAll(resp.Body)
+
if err != nil {
+
return nil, fmt.Errorf("could not read body for metadata response: %w", err)
+
}
+
+
var metadata OauthAuthorizationMetadata
+
if err := metadata.UnmarshalJSON(b); err != nil {
+
return nil, fmt.Errorf("could not unmarshal metadata: %w", err)
+
}
+
+
if err := metadata.Validate(u); err != nil {
+
return nil, fmt.Errorf("could not validate metadata: %w", err)
+
}
+
+
return metadata, nil
+
}
+
+
// func ClientAssertionJwt(clientId, authServerUrl string, clientSecretJwk jwk.Key) {
+
// clientAssertion := jwt.NewBuilder().Issuer(clientId).Subject(clientId).Audience(authServerUrl).IssuedAt(time.Now().Add()
+
// }
+
+
func isSafeAndParsed(ustr string) (*url.URL, error) {
+
u, err := url.Parse(ustr)
+
if err != nil {
+
return nil, err
+
}
+
+
if u.Scheme != "https" {
+
return nil, fmt.Errorf("input url is not https")
+
}
+
+
return u, nil
+
}
+32
oauth_test.go
···
+
package oauth
+
+
import (
+
"context"
+
"testing"
+
+
"github.com/stretchr/testify/assert"
+
)
+
+
var (
+
ctx = context.Background()
+
oauthClient = NewOauthClient(OauthClientArgs{})
+
)
+
+
func TestResolvePDSAuthServer(t *testing.T) {
+
assert := assert.New(t)
+
+
authServer, err := oauthClient.ResolvePDSAuthServer(ctx, "https://pds.haileyok.com")
+
+
assert.NoError(err)
+
assert.NotEmpty(authServer)
+
assert.Equal("https://pds.haileyok.com", authServer)
+
}
+
+
func TestFetchAuthServerMetadata(t *testing.T) {
+
assert := assert.New(t)
+
+
meta, err := oauthClient.FetchAuthServerMetadata(ctx, "https://pds.haileyok.com")
+
+
assert.NoError(err)
+
assert.IsType(OauthAuthorizationMetadata{}, meta)
+
}
+181
types.go
···
+
package oauth
+
+
import (
+
"encoding/json"
+
"fmt"
+
"net/url"
+
)
+
+
type OauthProtectedResource struct {
+
Resource string `json:"resource"`
+
AuthorizationServers []string `json:"authorization_servers"`
+
ScopesSupported []string `json:"scopes_supported"`
+
BearerMethodsSupported []string `json:"bearer_methods_supported"`
+
ResourceDocumentation string `json:"resource_documentation"`
+
}
+
+
func (opr *OauthProtectedResource) UnmarshalJSON(b []byte) error {
+
type Tmp OauthProtectedResource
+
var tmp Tmp
+
+
if err := json.Unmarshal(b, &tmp); err != nil {
+
return err
+
}
+
+
*opr = OauthProtectedResource(tmp)
+
+
return nil
+
}
+
+
type OauthAuthorizationMetadata struct {
+
Issuer string `json:"issuer"`
+
RequestParameterSupported bool `json:"request_parameter_supported"`
+
RequestUriParameterSupported bool `json:"request_uri_parameter_supported"`
+
RequireRequestUriRegistration *bool `json:"require_request_uri_registration,omitempty"`
+
ScopesSupported []string `json:"scopes_supported"`
+
SubjectTypesSupported []string `json:"subject_types_supported"`
+
ResponseTypesSupported []string `json:"response_types_supported"`
+
ResponseModesSupported []string `json:"response_modes_supported"`
+
GrantTypesSupported []string `json:"grant_types_supported"`
+
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
+
UILocalesSupported []string `json:"ui_locales_supported"`
+
DisplayValuesSupported []string `json:"display_values_supported"`
+
RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"`
+
AuthorizationResponseISSParameterSupported bool `json:"authorization_response_iss_parameter_supported"`
+
RequestObjectEncryptionAlgValuesSupported []string `json:"request_object_encryption_alg_values_supported"`
+
RequestObjectEncryptionEncValuesSupported []string `json:"request_object_encryption_enc_values_supported"`
+
JwksUri string `json:"jwks_uri"`
+
AuthorizationEndpoint string `json:"authorization_endpoint"`
+
TokenEndpoint string `json:"token_endpoint"`
+
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
+
TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported"`
+
RevocationEndpoint string `json:"revocation_endpoint"`
+
IntrospectionEndpoint string `json:"introspection_endpoint"`
+
PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint"`
+
RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests"`
+
DpopSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported"`
+
ProtectedResources []string `json:"protected_resources"`
+
ClientIDMetadataDocumentSupported bool `json:"client_id_metadata_document_supported"`
+
}
+
+
func (oam *OauthAuthorizationMetadata) Validate(fetch_url *url.URL) error {
+
if fetch_url == nil {
+
return fmt.Errorf("fetch_url was nil")
+
}
+
+
iu, err := url.Parse(oam.Issuer)
+
if err != nil {
+
oam = nil
+
return err
+
}
+
+
if iu.Hostname() != fetch_url.Hostname() {
+
oam = nil
+
return fmt.Errorf("issuer hostname does not match fetch url hostname")
+
}
+
+
if iu.Scheme != "https" {
+
oam = nil
+
return fmt.Errorf("issuer url is not https")
+
}
+
+
if iu.Port() != "" {
+
oam = nil
+
return fmt.Errorf("issuer port is not empty")
+
}
+
+
if iu.Path != "" && iu.Path != "/" {
+
oam = nil
+
return fmt.Errorf("issuer path is not /")
+
}
+
+
if iu.RawQuery != "" {
+
oam = nil
+
return fmt.Errorf("issuer url params are not empty")
+
}
+
+
if !tokenInSet("code", oam.ResponseTypesSupported) {
+
oam = nil
+
return fmt.Errorf("`code` is not in response_types_supported")
+
}
+
+
if !tokenInSet("authorization_code", oam.GrantTypesSupported) {
+
oam = nil
+
return fmt.Errorf("`authorization_code` is not in grant_types_supported")
+
}
+
+
if !tokenInSet("refresh_token", oam.GrantTypesSupported) {
+
oam = nil
+
return fmt.Errorf("`refresh_token` is not in grant_types_supported")
+
}
+
+
if !tokenInSet("S256", oam.CodeChallengeMethodsSupported) {
+
oam = nil
+
return fmt.Errorf("`S256` is not in code_challenge_methods_supported")
+
}
+
+
if !tokenInSet("none", oam.TokenEndpointAuthMethodsSupported) {
+
oam = nil
+
return fmt.Errorf("`none` is not in token_endpoint_auth_methods_supported")
+
}
+
+
if !tokenInSet("private_key_jwt", oam.TokenEndpointAuthMethodsSupported) {
+
oam = nil
+
return fmt.Errorf("`private_key_jwt` is not in token_endpoint_auth_methods_supported")
+
}
+
+
if !tokenInSet("ES256", oam.TokenEndpointAuthSigningAlgValuesSupported) {
+
oam = nil
+
return fmt.Errorf("`ES256` is not in token_endpoint_auth_signing_alg_values_supported")
+
}
+
+
if !tokenInSet("atproto", oam.ScopesSupported) {
+
oam = nil
+
return fmt.Errorf("`atproto` is not in scopes_supported")
+
}
+
+
if oam.AuthorizationResponseISSParameterSupported != true {
+
oam = nil
+
return fmt.Errorf("authorization_response_iss_parameter_supported is not true")
+
}
+
+
if oam.PushedAuthorizationRequestEndpoint == "" {
+
oam = nil
+
return fmt.Errorf("pushed_authorization_request_endpoint is empty")
+
}
+
+
if oam.RequirePushedAuthorizationRequests == false {
+
oam = nil
+
return fmt.Errorf("require_pushed_authorization_requests is false")
+
}
+
+
if !tokenInSet("ES256", oam.DpopSigningAlgValuesSupported) {
+
oam = nil
+
return fmt.Errorf("`ES256` is not in dpop_signing_alg_values_supported")
+
}
+
+
if oam.RequireRequestUriRegistration != nil && *oam.RequireRequestUriRegistration == false {
+
oam = nil
+
return fmt.Errorf("require_request_uri_registration present in metadata and was false")
+
}
+
+
if oam.ClientIDMetadataDocumentSupported == false {
+
oam = nil
+
return fmt.Errorf("client_id_metadata_document_supported was false")
+
}
+
+
return nil
+
}
+
+
func (oam *OauthAuthorizationMetadata) UnmarshalJSON(b []byte) error {
+
type Tmp OauthAuthorizationMetadata
+
var tmp Tmp
+
+
if err := json.Unmarshal(b, &tmp); err != nil {
+
return err
+
}
+
+
*oam = OauthAuthorizationMetadata(tmp)
+
+
return nil
+
}