this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

lots of progress

Hailey aaa3c98a 98153a3d

+345 -56
+3
.env.example
··· 1 + OAUTH_TEST_SERVER_ADDR=":7070" 2 + OAUTH_TEST_SERVER_URL_ROOT="http://localhost:7070" 3 + OAUTH_TEST_PDS_URL="https://pds.haileyok.com"
+2
.gitignore
··· 1 + cmd/client_test/client_test 2 + .env
+4
Makefile
··· 36 36 check: ## Compile everything, checking syntax (does not output binaries) 37 37 go build ./... 38 38 39 + .PHONY: test-server 40 + test-server: 41 + go run ./cmd/client_test 42 + 39 43 .env: 40 44 if [ ! -f ".env" ]; then cp example.dev.env .env; fi
+65
cmd/client_test/main.go
··· 1 + package main 2 + 3 + import ( 4 + "fmt" 5 + "log/slog" 6 + "net/http" 7 + 8 + "github.com/labstack/echo/v4" 9 + slogecho "github.com/samber/slog-echo" 10 + "github.com/urfave/cli/v2" 11 + ) 12 + 13 + func main() { 14 + app := &cli.App{ 15 + Name: "atproto-oauth-golang-tester", 16 + Action: run, 17 + } 18 + 19 + app.RunAndExitOnError() 20 + } 21 + 22 + func run(cmd *cli.Context) error { 23 + e := echo.New() 24 + 25 + e.Use(slogecho.New(slog.Default())) 26 + 27 + fmt.Println("atproto oauth golang tester server") 28 + 29 + e.GET("/oauth/client-metadata.json", handleClientMetadata) 30 + 31 + httpd := http.Server{ 32 + Addr: ":7070", 33 + Handler: e, 34 + } 35 + 36 + fmt.Println("starting http server...") 37 + 38 + if err := httpd.ListenAndServe(); err != nil { 39 + return err 40 + } 41 + 42 + return nil 43 + } 44 + 45 + func handleClientMetadata(e echo.Context) error { 46 + e.Response().Header().Add("Content-Type", "application/json") 47 + 48 + metadata := map[string]any{ 49 + "client_id": "http://localhost:7070/oauth/oauth-metadata.json", 50 + "client_name": "Atproto Oauth Golang Tester", 51 + "client_uri": "http://localhost:7070", 52 + "logo_uri": "http://localhost:7070/logo.png", 53 + "tos_uri": "http://localhost:7070/tos", 54 + "policy_url": "http://localhost:7070/policy", 55 + "redirect_uris": []string{"http://localhost:7070/callback"}, 56 + "grant_types": []string{"authorization_code", "refresh_token"}, 57 + "response_types": []string{"code"}, 58 + "application_type": "web", 59 + "token_endpoint_auth_method": "private_key_jwt", 60 + "dpop_bound_accesss_tokens": true, 61 + "jwks_uri": "http://localhost:7070/jwks.json", 62 + } 63 + 64 + return e.JSON(200, metadata) 65 + }
+17 -11
go.mod
··· 4 4 5 5 require ( 6 6 github.com/golang-jwt/jwt/v5 v5.2.1 7 - github.com/google/uuid v1.4.0 7 + github.com/google/uuid v1.6.0 8 + github.com/labstack/echo/v4 v4.13.3 8 9 github.com/lestrrat-go/jwx/v2 v2.0.12 9 10 github.com/stretchr/testify v1.10.0 11 + github.com/urfave/cli/v2 v2.27.5 10 12 ) 11 13 12 14 require ( 13 - github.com/bluesky-social/indigo v0.0.0-20250222003125-2503553ea604 // indirect 14 - github.com/davecgh/go-spew v1.1.1 // indirect 15 + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect 16 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 15 17 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 16 18 github.com/goccy/go-json v0.10.2 // indirect 17 - github.com/gorilla/context v1.1.2 // indirect 18 - github.com/gorilla/securecookie v1.1.2 // indirect 19 - github.com/gorilla/sessions v1.4.0 // indirect 20 - github.com/labstack/echo-contrib v0.17.2 // indirect 21 - github.com/labstack/echo/v4 v4.13.3 // indirect 19 + github.com/joho/godotenv v1.5.1 // indirect 20 + github.com/kr/pretty v0.3.1 // indirect 22 21 github.com/labstack/gommon v0.4.2 // indirect 23 - github.com/lestrrat-go/blackmagic v1.0.1 // indirect 22 + github.com/lestrrat-go/blackmagic v1.0.2 // indirect 24 23 github.com/lestrrat-go/httpcc v1.0.1 // indirect 25 24 github.com/lestrrat-go/httprc v1.0.4 // indirect 26 25 github.com/lestrrat-go/iter v1.0.2 // indirect 27 26 github.com/lestrrat-go/option v1.0.1 // indirect 28 27 github.com/mattn/go-colorable v0.1.13 // indirect 29 28 github.com/mattn/go-isatty v0.0.20 // indirect 30 - github.com/pmezard/go-difflib v1.0.0 // indirect 29 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 30 + github.com/rogpeppe/go-internal v1.13.1 // indirect 31 + github.com/russross/blackfriday/v2 v2.1.0 // indirect 32 + github.com/samber/lo v1.47.0 // indirect 33 + github.com/samber/slog-echo v1.15.1 // indirect 31 34 github.com/segmentio/asm v1.2.0 // indirect 32 35 github.com/valyala/bytebufferpool v1.0.0 // indirect 33 36 github.com/valyala/fasttemplate v1.2.2 // indirect 37 + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 38 + go.opentelemetry.io/otel v1.29.0 // indirect 39 + go.opentelemetry.io/otel/trace v1.29.0 // indirect 34 40 golang.org/x/crypto v0.31.0 // indirect 35 41 golang.org/x/net v0.33.0 // indirect 36 42 golang.org/x/sys v0.28.0 // indirect 37 43 golang.org/x/text v0.21.0 // indirect 38 - golang.org/x/time v0.8.0 // indirect 44 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 39 45 gopkg.in/yaml.v3 v3.0.1 // indirect 40 46 )
+40 -18
go.sum
··· 1 - github.com/bluesky-social/indigo v0.0.0-20250222003125-2503553ea604 h1:rceaPCufVEkobTmyISJhvY4kzaPKtSujN427CGWpvHw= 2 - github.com/bluesky-social/indigo v0.0.0-20250222003125-2503553ea604/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA= 1 + github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= 2 + github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 3 + github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 3 4 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 - github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 5 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 7 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 8 github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 7 9 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 8 10 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 9 11 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 10 12 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 11 - github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 12 13 github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 13 14 github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 14 - github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 15 - github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 16 - github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= 17 - github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= 18 - github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 19 - github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 20 - github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= 21 - github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 22 - github.com/labstack/echo-contrib v0.17.2 h1:K1zivqmtcC70X9VdBFdLomjPDEVHlrcAObqmuFj1c6w= 23 - github.com/labstack/echo-contrib v0.17.2/go.mod h1:NeDh3PX7j/u+jR4iuDt1zHmWZSCz9c/p9mxXcDpyS8E= 15 + github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 16 + github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 17 + github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 18 + github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 19 + github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 20 + github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 21 + github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 22 + github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 23 + github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 24 + github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 25 + github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 24 26 github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= 25 27 github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= 26 28 github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= 27 29 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 28 - github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= 29 30 github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 31 + github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= 32 + github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 30 33 github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= 31 34 github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= 32 35 github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= ··· 43 46 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 44 47 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 45 48 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 46 - github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 49 + github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 47 50 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 52 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 53 + github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 54 + github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 55 + github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 56 + github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 57 + github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 58 + github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= 59 + github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= 60 + github.com/samber/slog-echo v1.15.1 h1:mzeQNPYPxmpehIRtgQJRgJMVvrRbZHp5D2maxSljTBw= 61 + github.com/samber/slog-echo v1.15.1/go.mod h1:K21nbusPmai/MYm8PFactmZoFctkMmkeaTdXXyvhY1c= 48 62 github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 49 63 github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 50 64 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= ··· 56 70 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 57 71 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 58 72 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 73 + github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= 74 + github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= 59 75 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 60 76 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 61 77 github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 62 78 github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 79 + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= 80 + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= 63 81 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 82 + go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= 83 + go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= 84 + go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= 85 + go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= 64 86 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 65 87 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 66 88 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= ··· 104 126 golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 105 127 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 106 128 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 107 - golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= 108 - golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 109 129 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 110 130 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 111 131 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 112 132 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 113 133 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 114 134 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 135 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 136 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 115 137 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 116 138 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 117 139 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+181 -20
oauth.go
··· 1 1 package oauth 2 2 3 3 import ( 4 - "bytes" 5 4 "context" 6 5 "crypto/ecdsa" 7 6 "crypto/rand" ··· 12 11 "fmt" 13 12 "io" 14 13 "net/http" 14 + "net/url" 15 + "strings" 15 16 "time" 16 17 17 18 "github.com/golang-jwt/jwt/v5" ··· 111 112 return resource.AuthorizationServers[0], nil 112 113 } 113 114 114 - func (c *OauthClient) FetchAuthServerMetadata(ctx context.Context, ustr string) (any, error) { 115 + func (c *OauthClient) FetchAuthServerMetadata(ctx context.Context, ustr string) (*OauthAuthorizationMetadata, error) { 115 116 u, err := isSafeAndParsed(ustr) 116 117 if err != nil { 117 118 return nil, err ··· 149 150 return nil, fmt.Errorf("could not validate metadata: %w", err) 150 151 } 151 152 152 - return metadata, nil 153 + return &metadata, nil 153 154 } 154 155 155 156 func (c *OauthClient) ClientAssertionJwt(authServerUrl string) (string, error) { ··· 225 226 return tokenString, nil 226 227 } 227 228 228 - func (c *OauthClient) SendParAuthRequest(ctx context.Context, authServerUrl string, authServerMeta *OauthAuthorizationMetadata, loginHint, scope string, dpopPrivateKey jwk.Key) (any, error) { 229 + type SendParAuthResponse struct { 230 + PkceVerifier string 231 + State string 232 + DpopAuthserverNonce string 233 + Resp map[string]string 234 + } 235 + 236 + func (c *OauthClient) SendParAuthRequest(ctx context.Context, authServerUrl string, authServerMeta *OauthAuthorizationMetadata, loginHint, scope string, dpopPrivateKey jwk.Key) (*SendParAuthResponse, error) { 229 237 if authServerMeta == nil { 230 238 return nil, fmt.Errorf("nil metadata provided") 231 239 } ··· 257 265 return nil, err 258 266 } 259 267 260 - parBody := map[string]string{ 261 - "response_type": "code", 262 - "code_challenge": codeChallenge, 263 - "code_challenge_method": codeChallengeMethod, 264 - "client_id": c.clientId, 265 - "state": state, 266 - "redirect_uri": c.redirectUri, 267 - "scope": scope, 268 - "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", 269 - "client_assertion": clientAssertion, 268 + params := url.Values{ 269 + "response_type": {"code"}, 270 + "code_challenge": {codeChallenge}, 271 + "code_challenge_method": {codeChallengeMethod}, 272 + "client_id": {c.clientId}, 273 + "state": {state}, 274 + "redirect_uri": {c.redirectUri}, 275 + "scope": {scope}, 276 + "client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}, 277 + "client_assertion": {clientAssertion}, 270 278 } 271 279 272 280 if loginHint != "" { 273 - parBody["login_hint"] = loginHint 281 + params.Set("login_hint", loginHint) 274 282 } 275 283 276 284 _, err = isSafeAndParsed(parUrl) ··· 278 286 return nil, err 279 287 } 280 288 281 - b, err := json.Marshal(parBody) 289 + req, err := http.NewRequestWithContext(ctx, "POST", parUrl, strings.NewReader(params.Encode())) 290 + if err != nil { 291 + return nil, err 292 + } 293 + 294 + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 295 + req.Header.Set("DPoP", dpopProof) 296 + 297 + resp, err := c.h.Do(req) 298 + if err != nil { 299 + return nil, err 300 + } 301 + defer resp.Body.Close() 302 + 303 + var rmap map[string]string 304 + if err := json.NewDecoder(resp.Body).Decode(&rmap); err != nil { 305 + return nil, err 306 + } 307 + 308 + // TODO: there's some logic in the flask example where we retry if the server 309 + // asks us to use a dpop nonce. we should add that here eventually, but for now 310 + // we'll skip that 311 + 312 + return &SendParAuthResponse{ 313 + PkceVerifier: pkceVerifier, 314 + State: state, 315 + DpopAuthserverNonce: "", // add here later 316 + Resp: rmap, 317 + }, nil 318 + } 319 + 320 + type TokenResponse struct { 321 + DpopAuthserverNonce string 322 + Resp map[string]string 323 + } 324 + 325 + func (c *OauthClient) InitialTokenRequest(ctx context.Context, authRequest map[string]string, code, appUrl string) (*TokenResponse, error) { 326 + authserverUrl := authRequest["authserver_iss"] 327 + authserverMeta, err := c.FetchAuthServerMetadata(ctx, authserverUrl) 328 + if err != nil { 329 + return nil, err 330 + } 331 + 332 + clientAssertion, err := c.ClientAssertionJwt(authserverUrl) 333 + if err != nil { 334 + return nil, err 335 + } 336 + 337 + params := url.Values{ 338 + "client_id": {c.clientId}, 339 + "redirect_uri": {c.redirectUri}, 340 + "grant_type": {"authorization_code"}, 341 + "code": {code}, 342 + "code_verifier": {authRequest["pkce_verifier"]}, 343 + "client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}, 344 + "client_assertion": {clientAssertion}, 345 + } 346 + 347 + dpopPrivateJwk, err := parsePrivateJwkFromString(authRequest["dpop_private_jwk"]) 348 + if err != nil { 349 + return nil, err 350 + } 351 + 352 + dpopProof, err := c.AuthServerDpopJwt("POST", authserverMeta.TokenEndpoint, authRequest["dpop_authserver_nonce"], dpopPrivateJwk) 353 + if err != nil { 354 + return nil, err 355 + } 356 + 357 + dpopAuthserverNonce := authRequest["dpop_authserver_nonce"] 358 + 359 + req, err := http.NewRequestWithContext(ctx, "POST", authserverMeta.TokenEndpoint, strings.NewReader(params.Encode())) 282 360 if err != nil { 283 361 return nil, err 284 362 } 285 363 286 - req, err := http.NewRequestWithContext(ctx, "POST", parUrl, bytes.NewReader(b)) 364 + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 365 + req.Header.Set("DPoP", dpopProof) 366 + 367 + resp, err := c.h.Do(req) 287 368 if err != nil { 288 369 return nil, err 289 370 } 371 + defer resp.Body.Close() 290 372 291 - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 292 - req.Header.Add("DPoP", dpopProof) 373 + // TODO: use nonce if needed, same as in par 293 374 294 - return nil, nil 375 + var rmap map[string]string 376 + if err := json.NewDecoder(resp.Body).Decode(&rmap); err != nil { 377 + return nil, err 378 + } 379 + 380 + return &TokenResponse{ 381 + DpopAuthserverNonce: dpopAuthserverNonce, 382 + Resp: rmap, 383 + }, nil 384 + } 385 + 386 + type RefreshTokenArgs struct { 387 + AuthserverUrl string 388 + RefreshToken string 389 + DpopPrivateJwk string 390 + DpopAuthserverNonce string 391 + } 392 + 393 + func (c *OauthClient) RefreshTokenRequest(ctx context.Context, args RefreshTokenArgs, appUrl string) (any, error) { 394 + authserverMeta, err := c.FetchAuthServerMetadata(ctx, args.AuthserverUrl) 395 + if err != nil { 396 + return nil, err 397 + } 398 + 399 + clientAssertion, err := c.ClientAssertionJwt(args.AuthserverUrl) 400 + if err != nil { 401 + return nil, err 402 + } 403 + 404 + params := url.Values{ 405 + "client_id": {c.clientId}, 406 + "grant_type": {"refresh_token"}, 407 + "refresh_token": {args.RefreshToken}, 408 + "client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}, 409 + "client_assertion": {clientAssertion}, 410 + } 411 + 412 + dpopPrivateJwk, err := parsePrivateJwkFromString(args.DpopPrivateJwk) 413 + if err != nil { 414 + return nil, err 415 + } 416 + 417 + dpopProof, err := c.AuthServerDpopJwt("POST", authserverMeta.TokenEndpoint, args.DpopAuthserverNonce, dpopPrivateJwk) 418 + if err != nil { 419 + return nil, err 420 + } 421 + 422 + req, err := http.NewRequestWithContext(ctx, "POST", authserverMeta.TokenEndpoint, strings.NewReader(params.Encode())) 423 + if err != nil { 424 + return nil, err 425 + } 426 + 427 + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 428 + req.Header.Set("DPoP", dpopProof) 429 + 430 + resp, err := c.h.Do(req) 431 + if err != nil { 432 + return nil, err 433 + } 434 + defer resp.Body.Close() 435 + 436 + // TODO: handle same thing as above... 437 + 438 + if resp.StatusCode != 200 && resp.StatusCode != 201 { 439 + b, _ := io.ReadAll(resp.Body) 440 + return nil, fmt.Errorf("token refresh error: %s", string(b)) 441 + } 442 + 443 + var rmap map[string]string 444 + if err := json.NewDecoder(resp.Body).Decode(&rmap); err != nil { 445 + return nil, err 446 + } 447 + 448 + return &TokenResponse{ 449 + DpopAuthserverNonce: args.DpopAuthserverNonce, 450 + Resp: rmap, 451 + }, nil 295 452 } 296 453 297 454 func generateToken(len int) (string, error) { ··· 309 466 hash := h.Sum(nil) 310 467 return base64.RawURLEncoding.EncodeToString(hash) 311 468 } 469 + 470 + func parsePrivateJwkFromString(str string) (jwk.Key, error) { 471 + return jwk.ParseKey([]byte(str)) 472 + }
+33 -7
oauth_test.go
··· 3 3 import ( 4 4 "context" 5 5 "encoding/json" 6 + "fmt" 7 + "io" 8 + "net/http" 9 + "os" 6 10 "testing" 7 11 12 + _ "github.com/joho/godotenv/autoload" 8 13 "github.com/stretchr/testify/assert" 9 14 ) 10 15 11 16 var ( 12 - ctx = context.Background() 13 - oauthClient = newTestOauthClient() 17 + ctx = context.Background() 18 + oauthClient = newTestOauthClient() 19 + serverUrlRoot = os.Getenv("OAUTH_TEST_SERVER_URL_ROOT") 20 + serverMetadataUrl = fmt.Sprintf("%s/oauth/client-metadata.json", serverUrlRoot) 21 + serverCallbackUrl = fmt.Sprintf("%s/callback", serverUrlRoot) 22 + pdsUrl = os.Getenv("OAUTH_TEST_PDS_URL") 14 23 ) 15 24 16 25 func newTestOauthClient() *OauthClient { ··· 26 35 } 27 36 28 37 c, err := NewOauthClient(OauthClientArgs{ 29 - ClientJwk: b, 38 + ClientJwk: b, 39 + ClientId: serverMetadataUrl, 40 + RedirectUri: serverCallbackUrl, 30 41 }) 31 42 if err != nil { 32 43 panic(err) 33 44 } 34 45 46 + // make sure the server is running 47 + 48 + req, err := http.NewRequest("GET", serverMetadataUrl, nil) 49 + if err != nil { 50 + panic(err) 51 + } 52 + 53 + resp, err := http.DefaultClient.Do(req) 54 + if err != nil { 55 + panic(fmt.Errorf("could not connect to test server. are you sure you started it?")) 56 + } 57 + defer resp.Body.Close() 58 + 59 + io.Copy(io.Discard, resp.Body) 60 + 35 61 return c 36 62 } 37 63 38 64 func TestResolvePDSAuthServer(t *testing.T) { 39 65 assert := assert.New(t) 40 66 41 - authServer, err := oauthClient.ResolvePDSAuthServer(ctx, "https://pds.haileyok.com") 67 + authServer, err := oauthClient.ResolvePDSAuthServer(ctx, pdsUrl) 42 68 43 69 assert.NoError(err) 44 70 assert.NotEmpty(authServer) 45 - assert.Equal("https://pds.haileyok.com", authServer) 71 + assert.Equal(pdsUrl, authServer) 46 72 } 47 73 48 74 func TestFetchAuthServerMetadata(t *testing.T) { 49 75 assert := assert.New(t) 50 76 51 - meta, err := oauthClient.FetchAuthServerMetadata(ctx, "https://pds.haileyok.com") 77 + meta, err := oauthClient.FetchAuthServerMetadata(ctx, pdsUrl) 52 78 53 79 assert.NoError(err) 54 - assert.IsType(OauthAuthorizationMetadata{}, meta) 80 + assert.IsType(&OauthAuthorizationMetadata{}, meta) 55 81 } 56 82 57 83 func TestGenerateKey(t *testing.T) {