Discover books, shows, and movies at your level. Track your progress by filling your Shelf with what you find, and share with other language learners. *No dusting required. shlf.space

feat: add oauth redis store

Signed-off-by: brookjeynes <me@brookjeynes.dev>

+349 -1
+18 -1
go.mod
··· 3 go 1.25 4 5 require ( 6 - github.com/bluesky-social/indigo v0.0.0-20251223190123-598fbf0e146e 7 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e 8 ) 9 10 require ( 11 github.com/a-h/templ v0.3.977 // indirect 12 github.com/tdewolff/minify/v2 v2.24.8 // indirect 13 github.com/tdewolff/parse/v2 v2.8.5 // indirect 14 ) 15 16 require (
··· 3 go 1.25 4 5 require ( 6 + github.com/bluesky-social/indigo v0.0.0-20260220055544-bf41e2ee75ab 7 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e 8 ) 9 10 require ( 11 github.com/a-h/templ v0.3.977 // indirect 12 + github.com/beorn7/perks v1.0.1 // indirect 13 + github.com/cespare/xxhash/v2 v2.3.0 // indirect 14 + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 15 + github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect 16 + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect 17 + github.com/google/go-querystring v1.1.0 // indirect 18 + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 19 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 20 + github.com/prometheus/client_golang v1.17.0 // indirect 21 + github.com/prometheus/client_model v0.5.0 // indirect 22 + github.com/prometheus/common v0.45.0 // indirect 23 + github.com/prometheus/procfs v0.12.0 // indirect 24 + github.com/redis/go-redis/v9 v9.18.0 // indirect 25 github.com/tdewolff/minify/v2 v2.24.8 // indirect 26 github.com/tdewolff/parse/v2 v2.8.5 // indirect 27 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 28 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 29 + golang.org/x/time v0.3.0 // indirect 30 + google.golang.org/protobuf v1.33.0 // indirect 31 ) 32 33 require (
+39
go.sum
··· 2 github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg= 3 github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= 4 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 5 github.com/bluesky-social/indigo v0.0.0-20251223190123-598fbf0e146e h1:dEM6bfzMfkRI39GLinuhQan47HzdrkqIzJkl/zRvz8s= 6 github.com/bluesky-social/indigo v0.0.0-20251223190123-598fbf0e146e/go.mod h1:KIy0FgNQacp4uv2Z7xhNkV3qZiUSGuRky97s7Pa4v+o= 7 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 8 github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= 9 github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 10 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 14 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 15 github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= ··· 22 github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 23 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 24 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 25 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 26 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 27 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 28 github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 29 github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= ··· 37 github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 38 github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 39 github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 40 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= 41 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= 42 github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= ··· 88 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 89 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 90 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 91 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 92 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 93 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= ··· 113 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 114 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 115 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 116 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 117 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 118 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= ··· 155 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 156 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 157 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 158 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= 159 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= 160 go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= ··· 217 golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 218 golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 219 golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 220 golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 221 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 222 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 223 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 224 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 225 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 226 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= ··· 240 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 241 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 242 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 243 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 244 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 245 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
··· 2 github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg= 3 github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= 4 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 5 + github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 6 + github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 7 github.com/bluesky-social/indigo v0.0.0-20251223190123-598fbf0e146e h1:dEM6bfzMfkRI39GLinuhQan47HzdrkqIzJkl/zRvz8s= 8 github.com/bluesky-social/indigo v0.0.0-20251223190123-598fbf0e146e/go.mod h1:KIy0FgNQacp4uv2Z7xhNkV3qZiUSGuRky97s7Pa4v+o= 9 + github.com/bluesky-social/indigo v0.0.0-20260220055544-bf41e2ee75ab h1:Cs35T2tAN3Q6mMH5mBaY09nmCNOn/GkZS1F7jfMxlR8= 10 + github.com/bluesky-social/indigo v0.0.0-20260220055544-bf41e2ee75ab/go.mod h1:VG/LeqLGNI3Ew7lsYixajnZGFfWPv144qbUddh+Oyag= 11 + github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 12 + github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 13 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 14 github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= 15 github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 16 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 18 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 20 + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 21 + github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= 22 + github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= 23 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 24 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 25 github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= ··· 32 github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 33 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 34 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 35 + github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 36 + github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= 37 + github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 38 + github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 39 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 40 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 41 + github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 42 + github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 43 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 44 github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 45 github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= ··· 53 github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 54 github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 55 github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 56 + github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 57 + github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 58 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= 59 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= 60 github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= ··· 106 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 107 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 108 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 109 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= 110 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 111 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 112 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 113 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= ··· 133 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 134 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 135 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 136 + github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= 137 + github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= 138 + github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= 139 + github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= 140 + github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= 141 + github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= 142 + github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= 143 + github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 144 + github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= 145 + github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= 146 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 147 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 148 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= ··· 185 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 186 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 187 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 188 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 189 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 190 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 191 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 192 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= 193 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= 194 go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= ··· 251 golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 252 golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 253 golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 254 + golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= 255 golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 256 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 257 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 258 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 259 + golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 260 + golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 261 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 262 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 263 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= ··· 277 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 278 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 279 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 280 + google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 281 + google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 282 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 283 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 284 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+23
internal/config/config.go
··· 2 3 import ( 4 "context" 5 6 "github.com/sethvargo/go-envconfig" 7 ) ··· 18 ClientKid string `env:"CLIENT_KID"` 19 } 20 21 type JetstreamConfig struct { 22 Endpoint string `env:"ENDPOINT, default=wss://jetstream1.us-east.bsky.network/subscribe"` 23 } ··· 26 Core CoreConfig `env:",prefix=SHELF_"` 27 Jetstream JetstreamConfig `env:",prefix=SHELF_JETSTREAM_"` 28 OAuth OAuthConfig `env:",prefix=SHELF_OAUTH_"` 29 } 30 31 func LoadConfig(ctx context.Context) (*Config, error) {
··· 2 3 import ( 4 "context" 5 + "fmt" 6 + "net/url" 7 8 "github.com/sethvargo/go-envconfig" 9 ) ··· 20 ClientKid string `env:"CLIENT_KID"` 21 } 22 23 + type RedisConfig struct { 24 + Addr string `env:"ADDR, default=localhost:6379"` 25 + Password string `env:"PASS"` 26 + DB int `env:"DB, default=0"` 27 + } 28 + 29 + func (cfg RedisConfig) ToURL() string { 30 + u := &url.URL{ 31 + Scheme: "redis", 32 + Host: cfg.Addr, 33 + Path: fmt.Sprintf("/%d", cfg.DB), 34 + } 35 + 36 + if cfg.Password != "" { 37 + u.User = url.UserPassword("", cfg.Password) 38 + } 39 + 40 + return u.String() 41 + } 42 + 43 type JetstreamConfig struct { 44 Endpoint string `env:"ENDPOINT, default=wss://jetstream1.us-east.bsky.network/subscribe"` 45 } ··· 48 Core CoreConfig `env:",prefix=SHELF_"` 49 Jetstream JetstreamConfig `env:",prefix=SHELF_JETSTREAM_"` 50 OAuth OAuthConfig `env:",prefix=SHELF_OAUTH_"` 51 + Redis RedisConfig `env:",prefix=SHELF_REDIS_"` 52 } 53 54 func LoadConfig(ctx context.Context) (*Config, error) {
+269
internal/server/oauth/store.go
···
··· 1 + // MIT License 2 + // 3 + // Copyright (c) 2025 Anirudh Oppiliappan, Akshay Oppiliappan and 4 + // contributors. 5 + // 6 + // Permission is hereby granted, free of charge, to any person obtaining a copy 7 + // of this software and associated documentation files (the "Software"), to deal 8 + // in the Software without restriction, including without limitation the rights 9 + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 + // copies of the Software, and to permit persons to whom the Software is 11 + // furnished to do so, subject to the following conditions: 12 + // 13 + // The above copyright notice and this permission notice shall be included in all 14 + // copies or substantial portions of the Software. 15 + // 16 + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 + // SOFTWARE. 23 + 24 + package oauth 25 + 26 + import ( 27 + "context" 28 + "encoding/json" 29 + "fmt" 30 + "time" 31 + 32 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 33 + "github.com/bluesky-social/indigo/atproto/syntax" 34 + "github.com/redis/go-redis/v9" 35 + ) 36 + 37 + type RedisStoreConfig struct { 38 + RedisURL string 39 + 40 + // The purpose of these limits is to avoid dead sessions hanging around in the db indefinitely. 41 + // The durations here should be *at least as long as* the expected duration of the oauth session itself. 42 + SessionExpiryDuration time.Duration // duration since session creation (max TTL) 43 + SessionInactivityDuration time.Duration // duration since last session update 44 + AuthRequestExpiryDuration time.Duration // duration since auth request creation 45 + } 46 + 47 + // redis-backed implementation of ClientAuthStore. 48 + type RedisStore struct { 49 + client *redis.Client 50 + cfg *RedisStoreConfig 51 + } 52 + 53 + var _ oauth.ClientAuthStore = &RedisStore{} 54 + 55 + type sessionMetadata struct { 56 + CreatedAt time.Time `json:"created_at"` 57 + UpdatedAt time.Time `json:"updated_at"` 58 + } 59 + 60 + func NewRedisStore(cfg *RedisStoreConfig) (*RedisStore, error) { 61 + if cfg == nil { 62 + return nil, fmt.Errorf("missing cfg") 63 + } 64 + if cfg.RedisURL == "" { 65 + return nil, fmt.Errorf("missing RedisURL") 66 + } 67 + if cfg.SessionExpiryDuration == 0 { 68 + return nil, fmt.Errorf("missing SessionExpiryDuration") 69 + } 70 + if cfg.SessionInactivityDuration == 0 { 71 + return nil, fmt.Errorf("missing SessionInactivityDuration") 72 + } 73 + if cfg.AuthRequestExpiryDuration == 0 { 74 + return nil, fmt.Errorf("missing AuthRequestExpiryDuration") 75 + } 76 + 77 + opts, err := redis.ParseURL(cfg.RedisURL) 78 + if err != nil { 79 + return nil, fmt.Errorf("failed to parse redis URL: %w", err) 80 + } 81 + 82 + client := redis.NewClient(opts) 83 + 84 + // test the connection 85 + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 86 + defer cancel() 87 + 88 + if err := client.Ping(ctx).Err(); err != nil { 89 + return nil, fmt.Errorf("failed to connect to redis: %w", err) 90 + } 91 + 92 + return &RedisStore{ 93 + client: client, 94 + cfg: cfg, 95 + }, nil 96 + } 97 + 98 + func (r *RedisStore) Close() error { 99 + return r.client.Close() 100 + } 101 + 102 + func sessionKey(did syntax.DID, sessionID string) string { 103 + return fmt.Sprintf("oauth:session:%s:%s", did, sessionID) 104 + } 105 + 106 + func sessionMetadataKey(did syntax.DID, sessionID string) string { 107 + return fmt.Sprintf("oauth:session_meta:%s:%s", did, sessionID) 108 + } 109 + 110 + func authRequestKey(state string) string { 111 + return fmt.Sprintf("oauth:auth_request:%s", state) 112 + } 113 + 114 + func (r *RedisStore) GetSession(ctx context.Context, did syntax.DID, sessionID string) (*oauth.ClientSessionData, error) { 115 + key := sessionKey(did, sessionID) 116 + metaKey := sessionMetadataKey(did, sessionID) 117 + 118 + // Check metadata for inactivity expiry 119 + metaData, err := r.client.Get(ctx, metaKey).Bytes() 120 + if err == redis.Nil { 121 + return nil, fmt.Errorf("session not found: %s", did) 122 + } 123 + if err != nil { 124 + return nil, fmt.Errorf("failed to get session metadata: %w", err) 125 + } 126 + 127 + var meta sessionMetadata 128 + if err := json.Unmarshal(metaData, &meta); err != nil { 129 + return nil, fmt.Errorf("failed to unmarshal session metadata: %w", err) 130 + } 131 + 132 + // Check if session has been inactive for too long 133 + inactiveThreshold := time.Now().Add(-r.cfg.SessionInactivityDuration) 134 + if meta.UpdatedAt.Before(inactiveThreshold) { 135 + // Session is inactive, delete it 136 + r.client.Del(ctx, key, metaKey) 137 + return nil, fmt.Errorf("session expired due to inactivity: %s", did) 138 + } 139 + 140 + // Get the actual session data 141 + data, err := r.client.Get(ctx, key).Bytes() 142 + if err == redis.Nil { 143 + return nil, fmt.Errorf("session not found: %s", did) 144 + } 145 + if err != nil { 146 + return nil, fmt.Errorf("failed to get session: %w", err) 147 + } 148 + 149 + var sess oauth.ClientSessionData 150 + if err := json.Unmarshal(data, &sess); err != nil { 151 + return nil, fmt.Errorf("failed to unmarshal session: %w", err) 152 + } 153 + 154 + return &sess, nil 155 + } 156 + 157 + func (r *RedisStore) SaveSession(ctx context.Context, sess oauth.ClientSessionData) error { 158 + key := sessionKey(sess.AccountDID, sess.SessionID) 159 + metaKey := sessionMetadataKey(sess.AccountDID, sess.SessionID) 160 + 161 + data, err := json.Marshal(sess) 162 + if err != nil { 163 + return fmt.Errorf("failed to marshal session: %w", err) 164 + } 165 + 166 + // Check if session already exists to preserve CreatedAt 167 + var meta sessionMetadata 168 + existingMetaData, err := r.client.Get(ctx, metaKey).Bytes() 169 + if err == redis.Nil { 170 + // New session 171 + meta = sessionMetadata{ 172 + CreatedAt: time.Now(), 173 + UpdatedAt: time.Now(), 174 + } 175 + } else if err != nil { 176 + return fmt.Errorf("failed to check existing session metadata: %w", err) 177 + } else { 178 + // Existing session - preserve CreatedAt, update UpdatedAt 179 + if err := json.Unmarshal(existingMetaData, &meta); err != nil { 180 + return fmt.Errorf("failed to unmarshal existing session metadata: %w", err) 181 + } 182 + meta.UpdatedAt = time.Now() 183 + } 184 + 185 + // Calculate remaining TTL based on creation time 186 + remainingTTL := r.cfg.SessionExpiryDuration - time.Since(meta.CreatedAt) 187 + if remainingTTL <= 0 { 188 + return fmt.Errorf("session has expired") 189 + } 190 + 191 + // Use the shorter of: remaining TTL or inactivity duration 192 + ttl := min(r.cfg.SessionInactivityDuration, remainingTTL) 193 + 194 + // Save session data 195 + if err := r.client.Set(ctx, key, data, ttl).Err(); err != nil { 196 + return fmt.Errorf("failed to save session: %w", err) 197 + } 198 + 199 + // Save metadata 200 + metaData, err := json.Marshal(meta) 201 + if err != nil { 202 + return fmt.Errorf("failed to marshal session metadata: %w", err) 203 + } 204 + if err := r.client.Set(ctx, metaKey, metaData, ttl).Err(); err != nil { 205 + return fmt.Errorf("failed to save session metadata: %w", err) 206 + } 207 + 208 + return nil 209 + } 210 + 211 + func (r *RedisStore) DeleteSession(ctx context.Context, did syntax.DID, sessionID string) error { 212 + key := sessionKey(did, sessionID) 213 + metaKey := sessionMetadataKey(did, sessionID) 214 + 215 + if err := r.client.Del(ctx, key, metaKey).Err(); err != nil { 216 + return fmt.Errorf("failed to delete session: %w", err) 217 + } 218 + return nil 219 + } 220 + 221 + func (r *RedisStore) GetAuthRequestInfo(ctx context.Context, state string) (*oauth.AuthRequestData, error) { 222 + key := authRequestKey(state) 223 + data, err := r.client.Get(ctx, key).Bytes() 224 + if err == redis.Nil { 225 + return nil, fmt.Errorf("request info not found: %s", state) 226 + } 227 + if err != nil { 228 + return nil, fmt.Errorf("failed to get auth request: %w", err) 229 + } 230 + 231 + var req oauth.AuthRequestData 232 + if err := json.Unmarshal(data, &req); err != nil { 233 + return nil, fmt.Errorf("failed to unmarshal auth request: %w", err) 234 + } 235 + 236 + return &req, nil 237 + } 238 + 239 + func (r *RedisStore) SaveAuthRequestInfo(ctx context.Context, info oauth.AuthRequestData) error { 240 + key := authRequestKey(info.State) 241 + 242 + // check if already exists (to match MemStore behavior) 243 + exists, err := r.client.Exists(ctx, key).Result() 244 + if err != nil { 245 + return fmt.Errorf("failed to check auth request existence: %w", err) 246 + } 247 + if exists > 0 { 248 + return fmt.Errorf("auth request already saved for state %s", info.State) 249 + } 250 + 251 + data, err := json.Marshal(info) 252 + if err != nil { 253 + return fmt.Errorf("failed to marshal auth request: %w", err) 254 + } 255 + 256 + if err := r.client.Set(ctx, key, data, r.cfg.AuthRequestExpiryDuration).Err(); err != nil { 257 + return fmt.Errorf("failed to save auth request: %w", err) 258 + } 259 + 260 + return nil 261 + } 262 + 263 + func (r *RedisStore) DeleteAuthRequestInfo(ctx context.Context, state string) error { 264 + key := authRequestKey(state) 265 + if err := r.client.Del(ctx, key).Err(); err != nil { 266 + return fmt.Errorf("failed to delete auth request: %w", err) 267 + } 268 + return nil 269 + }