backend for xcvr appview
3
fork

Configure Feed

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

idk update to use new indigo/oauth

+382 -908
+28
migrations/003_newoauth.down.sql
··· 1 + DROP TABLE IF EXISTS sessions; 2 + DROP TABLE IF EXISTS requests; 3 + 4 + CREATE TABLE oauthrequests ( 5 + id SERIAL PRIMARY KEY, 6 + authserver_iss TEXT, 7 + state TEXT, 8 + did TEXT, 9 + pds_url TEXT, 10 + pkce_verifier TEXT, 11 + dpop_auth_server_nonce TEXT, 12 + dpop_private_jwk TEXT 13 + ); 14 + 15 + CREATE TABLE oauthsessions ( 16 + id SERIAL PRIMARY KEY, 17 + authserver_iss TEXT, 18 + state TEXT, 19 + did TEXT, 20 + pds_url TEXT, 21 + pkce_verifier TEXT, 22 + dpop_auth_server_nonce TEXT, 23 + dpop_private_jwk TEXT, 24 + dpop_pds_nonce TEXT, 25 + access_token TEXT, 26 + refresh_token TEXT, 27 + expiration TIMESTAMPTZ 28 + );
+28
migrations/003_newoauth.up.sql
··· 1 + DROP TABLE IF EXISTS oauthsessions; 2 + DROP TABLE IF EXISTS oauthrequests; 3 + 4 + CREATE TABLE requests ( 5 + state TEXT PRIMARY KEY, 6 + authserver_url TEXT NOT NULL, 7 + account_did TEXT, 8 + scopes TEXT NOT NULL, 9 + request_uri TEXT NOT NULL, 10 + authserver_token_endpoint TEXT NOT NULL, 11 + pkce_verifier TEXT NOT NULL, 12 + dpop_authserver_nonce TEXT NOT NULL, 13 + dpop_privatekey_multibase TEXT NOT NULL 14 + ); 15 + 16 + CREATE TABLE sessions ( 17 + session_id TEXT NOT NULL PRIMARY KEY, 18 + account_did TEXT NOT NULL, 19 + host_url TEXT NOT NULL, 20 + authserver_url TEXT NOT NULL, 21 + authserver_token_endpoint TEXT NOT NULL, 22 + scopes TEXT NOT NULL, 23 + access_token TEXT NOT NULL, 24 + refresh_token TEXT NOT NULL, 25 + dpop_authserver_nonce TEXT NOT NULL, 26 + dpop_host_nonce TEXT NOT NULL, 27 + dpop_privatekey_multibase TEXT NOT NULL 28 + )
+1 -8
server/cmd/main.go
··· 13 13 "rvcx/internal/model" 14 14 "rvcx/internal/oauth" 15 15 "rvcx/internal/recordmanager" 16 - "time" 17 16 18 17 "github.com/joho/godotenv" 19 18 ) ··· 45 44 if err != nil { 46 45 panic(err) 47 46 } 48 - httpclient := &http.Client{ 49 - Timeout: 5 * time.Second, 50 - Transport: &http.Transport{ 51 - IdleConnTimeout: 90 * time.Second, 52 - }, 53 - } 54 - oauthclient, err := oauth.NewService(httpclient) 47 + oauthclient, err := oauth.NewService(*store) 55 48 if err != nil { 56 49 logger.Println(err.Error()) 57 50 panic(err)
+15
server/crypto/main.go
··· 1 + package main 2 + 3 + import ( 4 + "fmt" 5 + "github.com/bluesky-social/indigo/atproto/crypto" 6 + ) 7 + 8 + func main() { 9 + privateKey, err := crypto.GeneratePrivateKeyK256() 10 + if err != nil { 11 + panic(err) 12 + } 13 + clientSecretKey := privateKey.Multibase() 14 + fmt.Println(clientSecretKey) 15 + }
+5 -36
server/go.mod
··· 3 3 go 1.24.2 4 4 5 5 require ( 6 - github.com/bluesky-social/indigo v0.0.0-20250616202859-d4516ea1d6cf 6 + github.com/bluesky-social/indigo v0.0.0-20250813051257-8be102876fb7 7 7 github.com/bluesky-social/jetstream v0.0.0-20250414024304-d17bd81a945e 8 8 github.com/gorilla/sessions v1.4.0 9 9 github.com/gorilla/websocket v1.5.3 10 - github.com/haileyok/atproto-oauth-golang v0.0.2 11 10 github.com/ipfs/go-cid v0.4.1 12 11 github.com/jackc/pgx/v5 v5.7.4 13 12 github.com/joho/godotenv v1.5.1 14 - github.com/lestrrat-go/jwx/v2 v2.0.12 15 13 github.com/rachel-mp4/lrcd v0.0.0-20250731224514-2f8d47fe368c 16 14 github.com/rachel-mp4/lrcproto v0.0.0-20250720164211-c6162669b709 17 15 github.com/rivo/uniseg v0.4.7 ··· 23 21 github.com/beorn7/perks v1.0.1 // indirect 24 22 github.com/carlmjohnson/versioninfo v0.22.5 // indirect 25 23 github.com/cespare/xxhash/v2 v2.3.0 // indirect 26 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 27 - github.com/felixge/httpsnoop v1.0.4 // indirect 28 - github.com/go-logr/logr v1.4.2 // indirect 29 - github.com/go-logr/stdr v1.2.2 // indirect 24 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 30 25 github.com/goccy/go-json v0.10.2 // indirect 31 - github.com/gogo/protobuf v1.3.2 // indirect 32 26 github.com/golang-jwt/jwt/v5 v5.2.2 // indirect 33 - github.com/google/uuid v1.6.0 // indirect 27 + github.com/google/go-querystring v1.1.0 // indirect 34 28 github.com/gorilla/securecookie v1.1.2 // indirect 35 - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 36 - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect 37 - github.com/hashicorp/golang-lru v1.0.2 // indirect 38 29 github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 39 - github.com/ipfs/bbloom v0.0.4 // indirect 40 - github.com/ipfs/go-block-format v0.2.0 // indirect 41 - github.com/ipfs/go-datastore v0.6.0 // indirect 42 - github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect 43 - github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect 44 - github.com/ipfs/go-ipfs-util v0.0.3 // indirect 45 - github.com/ipfs/go-ipld-cbor v0.1.0 // indirect 46 - github.com/ipfs/go-ipld-format v0.6.0 // indirect 47 - github.com/ipfs/go-log v1.0.5 // indirect 48 - github.com/ipfs/go-log/v2 v2.5.1 // indirect 49 - github.com/ipfs/go-metrics-interface v0.0.1 // indirect 50 30 github.com/jackc/pgpassfile v1.0.0 // indirect 51 31 github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect 52 32 github.com/jackc/puddle/v2 v2.2.2 // indirect 53 - github.com/jbenet/goprocess v0.1.4 // indirect 54 33 github.com/klauspost/compress v1.17.9 // indirect 55 34 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 56 - github.com/lestrrat-go/blackmagic v1.0.2 // indirect 57 - github.com/lestrrat-go/httpcc v1.0.1 // indirect 58 - github.com/lestrrat-go/httprc v1.0.4 // indirect 59 - github.com/lestrrat-go/iter v1.0.2 // indirect 60 - github.com/lestrrat-go/option v1.0.1 // indirect 61 - github.com/mattn/go-isatty v0.0.20 // indirect 62 35 github.com/minio/sha256-simd v1.0.1 // indirect 63 36 github.com/mr-tron/base58 v1.2.0 // indirect 64 37 github.com/multiformats/go-base32 v0.1.0 // indirect ··· 66 39 github.com/multiformats/go-multibase v0.2.0 // indirect 67 40 github.com/multiformats/go-multihash v0.2.3 // indirect 68 41 github.com/multiformats/go-varint v0.0.7 // indirect 69 - github.com/opentracing/opentracing-go v1.2.0 // indirect 70 - github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 42 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 71 43 github.com/prometheus/client_golang v1.19.1 // indirect 72 44 github.com/prometheus/client_model v0.6.1 // indirect 73 45 github.com/prometheus/common v0.54.0 // indirect 74 46 github.com/prometheus/procfs v0.15.1 // indirect 75 - github.com/segmentio/asm v1.2.0 // indirect 76 47 github.com/spaolacci/murmur3 v1.1.0 // indirect 48 + github.com/stretchr/testify v1.10.0 // indirect 77 49 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 78 50 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 79 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect 80 51 go.opentelemetry.io/otel v1.29.0 // indirect 81 52 go.opentelemetry.io/otel/metric v1.29.0 // indirect 82 53 go.opentelemetry.io/otel/trace v1.29.0 // indirect 83 54 go.uber.org/atomic v1.11.0 // indirect 84 - go.uber.org/multierr v1.11.0 // indirect 85 - go.uber.org/zap v1.26.0 // indirect 86 55 golang.org/x/crypto v0.31.0 // indirect 87 56 golang.org/x/sync v0.10.0 // indirect 88 57 golang.org/x/sys v0.28.0 // indirect
+5 -162
server/go.sum
··· 1 - github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 - github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 1 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 4 2 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 5 - github.com/bluesky-social/indigo v0.0.0-20250616202859-d4516ea1d6cf h1:LFlwtY9r95lAI1yYKolCLTQnwK5VjgWO87mNsKdj3Qs= 6 - github.com/bluesky-social/indigo v0.0.0-20250616202859-d4516ea1d6cf/go.mod h1:8FlFpF5cIq3DQG0kEHqyTkPV/5MDQoaWLcVwza5ZPJU= 3 + github.com/bluesky-social/indigo v0.0.0-20250813051257-8be102876fb7 h1:FyoGfQFw/cTkDHdUTIYIHxfyUDgRS12K4o1mYC3ovRs= 4 + github.com/bluesky-social/indigo v0.0.0-20250813051257-8be102876fb7/go.mod h1:n6QE1NDPFoi7PRbMUZmc2y7FibCqiVU4ePpsvhHUBR8= 7 5 github.com/bluesky-social/jetstream v0.0.0-20250414024304-d17bd81a945e h1:P/O6TDHs53gwgV845uDHI+Nri889ixksRrh4bCkCdxo= 8 6 github.com/bluesky-social/jetstream v0.0.0-20250414024304-d17bd81a945e/go.mod h1:WiYEeyJSdUwqoaZ71KJSpTblemUCpwJfh5oVXplK6T4= 9 7 github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= 10 8 github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= 11 9 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 12 10 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 11 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 - github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 12 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 17 13 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 - github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 19 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 20 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 21 14 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 22 15 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 23 - github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 24 16 github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 25 17 github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 26 18 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 27 19 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 28 - github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 29 20 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 30 21 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 31 22 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 32 23 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 33 24 github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= 34 25 github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 26 + github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 35 27 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 36 28 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 29 + github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 30 + github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 37 31 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 38 32 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 39 - github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 40 33 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 41 34 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 42 - github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 43 - github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 44 35 github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 45 36 github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 46 37 github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= ··· 49 40 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 50 41 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 51 42 github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 52 - github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= 53 - github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 54 43 github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= 55 44 github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 56 45 github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= ··· 65 54 github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= 66 55 github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= 67 56 github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= 68 - github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= 69 - github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= 70 57 github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= 71 58 github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= 72 59 github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw= ··· 79 66 github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg= 80 67 github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= 81 68 github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= 82 - github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= 83 69 github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= 84 70 github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= 85 71 github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= ··· 92 78 github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= 93 79 github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= 94 80 github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 95 - github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= 96 81 github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= 97 82 github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 98 83 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 99 84 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 100 - github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 101 - github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 102 - github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 103 - github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 104 85 github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 105 86 github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 106 87 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 107 88 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 108 - github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 109 - github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 110 - github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 111 - github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 112 - github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 113 - github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 114 - github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 115 - github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 116 - github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= 117 - github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 118 - github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= 119 - github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= 120 - github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= 121 - github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= 122 - github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= 123 - github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= 124 - github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA= 125 - github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ= 126 - github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 127 - github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= 128 - github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 129 - github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 130 89 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 131 90 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 132 91 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= ··· 145 104 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 146 105 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 147 106 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 148 - github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 149 107 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 150 108 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 151 109 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= ··· 159 117 github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= 160 118 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 161 119 github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 162 - github.com/rachel-mp4/atproto-oauth-golang v0.0.0-20250616212213-a55a5f62b82d h1:FQ8YKfXnKmyEbKnO/blj3qWGhYdw+l3DtQCqSboJRvA= 163 - github.com/rachel-mp4/atproto-oauth-golang v0.0.0-20250616212213-a55a5f62b82d/go.mod h1:vVRo6BPEmWOZnYk9LtXLzBPzfkY63fUaBahA+o4h55Q= 164 120 github.com/rachel-mp4/lrcd v0.0.0-20250731224514-2f8d47fe368c h1:/KXAMnA+PC9ihDVI2u4BQ5BITsR+WB0anx3Zk/sLW88= 165 121 github.com/rachel-mp4/lrcd v0.0.0-20250731224514-2f8d47fe368c/go.mod h1:lU5b8bC7vP56MT57i6gUYoXxHSVLyrUYMnUo7JELwSc= 166 122 github.com/rachel-mp4/lrcproto v0.0.0-20250720164211-c6162669b709 h1:P//gJE0zFv9Qvfn8dvp9ZrnG0FZh2MVcAX+uOP2flRw= 167 123 github.com/rachel-mp4/lrcproto v0.0.0-20250720164211-c6162669b709/go.mod h1:hQzO36tQELGbkmRnUtKeM6NMU34t79ZcTlhM+MO7pHw= 168 124 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 169 125 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 170 - github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 171 - github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 172 - github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 173 - github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 174 - github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 175 - github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 176 - github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 177 - github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 178 - github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 179 - github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= 180 - github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= 181 126 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 182 127 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 183 128 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 184 - github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 185 - github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 186 - github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 187 129 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 188 - github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 189 - github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 190 130 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 191 - github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 192 - github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 193 - github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 194 131 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 195 132 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 196 - github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 197 - github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= 198 - github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= 199 133 github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0= 200 134 github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 201 - github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 202 - github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 203 - github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 204 - github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 205 135 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 206 136 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 207 137 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= ··· 214 144 go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= 215 145 go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= 216 146 go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= 217 - go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 218 - go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 219 147 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 220 148 go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 221 - go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 222 - go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 223 - go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= 224 - go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 225 - go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 226 149 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 227 150 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 228 - go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 229 - go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= 230 - go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 231 151 go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 232 152 go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 233 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 234 - golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 235 - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 236 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 237 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 238 - golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 239 153 golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 240 154 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 241 - golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 242 - golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 243 - golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 244 - golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 245 - golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 246 - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 247 - golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 248 - golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 249 - golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 250 - golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 251 - golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 252 - golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 253 - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 254 - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 255 - golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 256 - golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 257 - golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 258 - golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 259 - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 260 - golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 261 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 262 - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 263 - golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 264 155 golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 265 156 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 266 - golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 267 - golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 268 - golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 269 - golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 270 - golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 271 - golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 272 - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 273 - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 274 - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 275 - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 276 - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 277 157 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 278 - golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 279 - golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 280 - golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 281 158 golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 282 159 golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 283 - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 284 - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 285 - golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 286 - golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 287 - golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 288 - golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 289 - golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 290 - golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 291 - golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 292 - golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 293 - golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 294 160 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 295 161 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 296 162 golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= 297 163 golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 298 - golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 299 - golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 300 - golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 301 - golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 302 - golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 303 - golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 304 - golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 305 - golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 306 - golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 307 - golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 308 - golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 309 - golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 310 - golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 311 - golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 312 164 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 313 - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 314 165 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 315 166 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 316 167 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 317 168 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 318 169 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 319 - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 320 - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 321 - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 322 - gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 323 - gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 324 - gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 325 170 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 326 - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 327 171 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 328 172 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 329 - honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 330 173 lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= 331 174 lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
+121 -111
server/internal/db/oauth.go
··· 4 4 "context" 5 5 "errors" 6 6 "fmt" 7 - "rvcx/internal/types" 7 + "strings" 8 + 9 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 10 + "github.com/bluesky-social/indigo/atproto/syntax" 8 11 ) 9 12 10 - func (s *Store) StoreOAuthRequest(req *types.OAuthRequest, ctx context.Context) error { 11 - _, err := s.pool.Exec(ctx, ` 12 - INSERT INTO oauthrequests ( 13 - authserver_iss, 14 - state, 15 - did, 16 - pds_url, 17 - pkce_verifier, 18 - dpop_auth_server_nonce, 19 - dpop_private_jwk 20 - ) VALUES ($1, $2, $3, $4, $5, $6, $7)`, 21 - req.AuthserverIss, 22 - req.State, 23 - req.Did, 24 - req.PdsUrl, 25 - req.PkceVerifier, 26 - req.DpopAuthServerNonce, 27 - req.DpopPrivKey) 28 - return err 13 + func (s Store) GetSession(ctx context.Context, did syntax.DID, sessionID string) (*oauth.ClientSessionData, error) { 14 + row := s.pool.QueryRow(ctx, ` 15 + SELECT 16 + host_url, 17 + authserver_url, 18 + authserver_token_endpoint, 19 + scopes, 20 + access_token, 21 + refresh_token, 22 + dpop_authserver_nonce, 23 + dpop_host_nonce, 24 + dpop_privatekey_multibase 25 + FROM sessions 26 + WHERE did = $1 AND session_id = $2`, did.String(), sessionID) 27 + var scope string 28 + var csd oauth.ClientSessionData 29 + csd.AccountDID = did 30 + csd.SessionID = sessionID 31 + err := row.Scan(&csd.HostURL, 32 + &csd.AuthServerURL, 33 + &csd.AuthServerTokenEndpoint, 34 + &scope, 35 + &csd.AccessToken, 36 + &csd.RefreshToken, 37 + &csd.DPoPAuthServerNonce, 38 + &csd.DPoPHostNonce, 39 + &csd.DPoPPrivateKeyMultibase, 40 + ) 41 + if err != nil { 42 + return nil, errors.New("error scanning: " + err.Error()) 43 + } 44 + scopes := strings.Fields(scope) 45 + csd.Scopes = scopes 46 + return &csd, nil 29 47 } 30 48 31 - func (s *Store) StoreOAuthSession(session *types.Session, ctx context.Context) error { 49 + func (s Store) SaveSession(ctx context.Context, sess oauth.ClientSessionData) error { 50 + scope := strings.Join(sess.Scopes, " ") 32 51 _, err := s.pool.Exec(ctx, ` 33 - INSERT INTO oauthsessions ( 34 - id, 35 - authserver_iss, 36 - state, 37 - did, 38 - pds_url, 39 - pkce_verifier, 40 - dpop_auth_server_nonce, 41 - dpop_private_jwk, 42 - dpop_pds_nonce, 52 + INSERT INTO sessions ( 53 + session_id, 54 + account_did, 55 + host_url, 56 + authserver_url, 57 + authserver_token_endpoint, 58 + scopes, 43 59 access_token, 44 60 refresh_token, 45 - expiration 46 - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, 47 - session.ID, 48 - session.AuthserverIss, 49 - session.State, 50 - session.Did, 51 - session.PdsUrl, 52 - session.PkceVerifier, 53 - session.DpopAuthServerNonce, 54 - session.DpopPrivKey, 55 - session.DpopPdsNonce, 56 - session.AccessToken, 57 - session.RefreshToken, 58 - session.Expiration) 61 + dpop_authserver_nonce, 62 + dpop_host_nonce, 63 + dpop_privatekey_multibase 64 + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, 65 + sess.SessionID, 66 + sess.AccountDID.String(), 67 + sess.HostURL, 68 + sess.AuthServerURL, 69 + sess.AuthServerTokenEndpoint, 70 + scope, 71 + sess.AccessToken, 72 + sess.RefreshToken, 73 + sess.DPoPAuthServerNonce, 74 + sess.DPoPHostNonce, 75 + sess.DPoPPrivateKeyMultibase, 76 + ) 59 77 if err != nil { 60 - return errors.New("error storing oauth session" + err.Error()) 78 + return errors.New("failed to insert: " + err.Error()) 61 79 } 62 80 return nil 63 81 } 64 82 65 - func (s *Store) UpdateSession(id int, session *types.Session, ctx context.Context) error { 66 - _, err := s.pool.Exec(ctx, `UPDATE oauthsessions 67 - SET (dpop_auth_server_nonce, access_token, refresh_token) 68 - VALUES ($1, $2, $3) 69 - WHERE id = $4 70 - `, session.DpopAuthServerNonce, session.AccessToken, session.RefreshToken, id) 83 + func (s Store) DeleteSession(ctx context.Context, did syntax.DID, sessionID string) error { 84 + _, err := s.pool.Exec(ctx, `DELETE FROM sessions WHERE account_did = $1 AND session_id = $2`, did.String(), sessionID) 71 85 if err != nil { 72 - return errors.New("error updating session: " + err.Error()) 86 + return errors.New("failed to delete: " + err.Error()) 73 87 } 74 88 return nil 75 89 } 76 90 77 - func (s *Store) GetOauthRequest(state string, ctx context.Context) (*types.OAuthRequest, error) { 91 + func (s Store) GetAuthRequestInfo(ctx context.Context, state string) (*oauth.AuthRequestData, error) { 78 92 row := s.pool.QueryRow(ctx, ` 79 - SELECT 80 - r.id, 81 - r.authserver_iss, 82 - r.did, 83 - r.pds_url, 84 - r.pkce_verifier, 85 - r.dpop_auth_server_nonce, 86 - r.dpop_private_jwk 87 - FROM oauthrequests r 88 - WHERE r.state = $1 89 - LIMIT 1 93 + SELECT 94 + authserver_url, 95 + account_did, 96 + scopes, 97 + request_uri, 98 + authserver_token_endpoint, 99 + pkce_verifier, 100 + dpop_authserver_nonce, 101 + dpop_privatekey_multibase 102 + FROM requests 103 + WHERE state = $1 90 104 `, state) 91 - var req types.OAuthRequest 92 - err := row.Scan(&req.ID, &req.AuthserverIss, &req.Did, &req.PdsUrl, &req.PkceVerifier, &req.DpopAuthServerNonce, &req.DpopPrivKey) 105 + var ari oauth.AuthRequestData 106 + ari.State = state 107 + var did string 108 + err := row.Scan( 109 + &ari.AuthServerURL, 110 + &did, 111 + &ari.Scope, 112 + &ari.AuthServerTokenEndpoint, 113 + &ari.PKCEVerifier, 114 + &ari.DPoPAuthServerNonce, 115 + &ari.DPoPPrivateKeyMultibase, 116 + ) 93 117 if err != nil { 94 - return nil, errors.New("error scanning rows while getting oauth request:" + err.Error()) 118 + return nil, errors.New("failed to scan: " + err.Error()) 95 119 } 96 - return &req, nil 97 - } 98 - 99 - func (s *Store) GetOauthSession(id int, ctx context.Context) (*types.Session, error) { 100 - row := s.pool.QueryRow(ctx, ` 101 - SELECT 102 - r.authserver_iss, 103 - r.did, 104 - r.pds_url, 105 - r.pkce_verifier, 106 - r.dpop_auth_server_nonce, 107 - r.dpop_private_jwk, 108 - r.dpop_pds_nonce, 109 - r.access_token, 110 - r.refresh_token, 111 - r.expiration 112 - FROM oauthsessions r 113 - WHERE r.id = $1 114 - `, id) 115 - var session types.Session 116 - err := row.Scan( 117 - &session.AuthserverIss, 118 - &session.Did, 119 - &session.PdsUrl, 120 - &session.PkceVerifier, 121 - &session.DpopAuthServerNonce, 122 - &session.DpopPrivKey, 123 - &session.DpopPdsNonce, 124 - &session.AccessToken, 125 - &session.RefreshToken, 126 - &session.Expiration) 120 + sdid, err := syntax.ParseDID(did) 127 121 if err != nil { 128 - return nil, errors.New("error scanning oauthsession row: " + err.Error()) 122 + return nil, errors.New("failed to parse did: " + err.Error()) 129 123 } 130 - session.ID = id 131 - return &session, nil 124 + ari.AccountDID = &sdid 125 + return &ari, nil 132 126 } 133 127 134 - func (s *Store) DeleteOauthRequest(state string, ctx context.Context) error { 128 + func (s Store) SaveAuthRequestInfo(ctx context.Context, info oauth.AuthRequestData) error { 135 129 _, err := s.pool.Exec(ctx, ` 136 - DELETE FROM oauthrequests r WHERE r.state = $1 137 - `, state) 130 + INSERT INTO requests ( 131 + state, 132 + authserver_url, 133 + account_did, 134 + scopes, 135 + request_uri, 136 + authserver_token_endpoint, 137 + pkce_verifier, 138 + dpop_authserver_nonce, 139 + dpop_privatekey_multibase) 140 + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, 141 + info.State, 142 + info.AuthServerURL, 143 + info.AccountDID.String(), 144 + info.Scope, 145 + info.AuthServerTokenEndpoint, 146 + info.PKCEVerifier, 147 + info.DPoPAuthServerNonce, 148 + info.DPoPPrivateKeyMultibase, 149 + ) 138 150 if err != nil { 139 - return errors.New("error deleting oauth request:" + err.Error()) 151 + return errors.New("failed to insert: " + err.Error()) 140 152 } 141 153 return nil 142 154 } 143 155 144 - func (s *Store) DeleteOauthSession(id int, ctx context.Context) error { 145 - _, err := s.pool.Exec(ctx, ` 146 - DELETE FROM oauthsessions s WHERE s.id = $1 147 - `, id) 156 + func (s Store) DeleteAuthRequestInfo(ctx context.Context, state string) error { 157 + _, err := s.pool.Exec(ctx, `DELETE FROM requests WHERE state = $1`, state) 148 158 if err != nil { 149 - return errors.New("error deleting oauth request:" + err.Error()) 159 + return errors.New("failed to delete: " + err.Error()) 150 160 } 151 161 return nil 152 162 }
+2 -2
server/internal/handler/lrcHandlers.go
··· 32 32 return 33 33 } 34 34 session, _ := h.sessionStore.Get(r, "oauthsession") 35 - id, ok := session.Values["id"].(int) 35 + id, ok := session.Values["id"].(string) 36 36 var uri, did string 37 37 if !ok { 38 38 did, uri, err = h.rm.PostMyChannel(r.Context(), cr) ··· 88 88 return 89 89 } 90 90 session, _ := h.sessionStore.Get(r, "oauthsession") 91 - id, ok := session.Values["id"].(int) 91 + id, ok := session.Values["id"].(string) 92 92 if !ok { 93 93 err = h.rm.PostMyMessage(r.Context(), pmr) 94 94 } else {
+20 -115
server/internal/handler/oauthHandlers.go
··· 1 1 package handler 2 2 3 3 import ( 4 - "context" 5 4 "encoding/json" 6 5 "errors" 7 6 "fmt" 8 7 "net/http" 9 - "net/url" 10 8 "os" 11 9 "rvcx/internal/atputils" 12 10 "rvcx/internal/oauth" 11 + "strings" 13 12 14 13 "github.com/gorilla/sessions" 15 - // aog "github.com/haileyok/atproto-oauth-golang" 16 - "github.com/haileyok/atproto-oauth-golang/helpers" 17 14 ) 18 15 19 16 func (h *Handler) serveJWKS(w http.ResponseWriter, r *http.Request) { 20 - key, err := oauth.GetJWKS() 17 + key, err := oauth.GetPrivateKey() 21 18 if err != nil { 22 19 h.serverError(w, err) 23 20 } ··· 25 22 if err != nil { 26 23 h.serverError(w, err) 27 24 } 28 - ro := helpers.CreateJwksResponseObject(pubKey) 25 + ro, err := pubKey.JWK() 26 + if err != nil { 27 + h.serverError(w, err) 28 + } 29 29 w.Header().Set("Content-Type", "application/json") 30 30 encoder := json.NewEncoder(w) 31 31 encoder.Encode(ro) 32 32 } 33 33 34 - // func (h *Handler) newOAuthLogin(w http.ResponseWriter, r *http.Request) { 35 - // err := r.ParseForm() 36 - // if err != nil { 37 - // h.badRequest(w, err) 38 - // return 39 - // } 40 - // clientID := oauth.GetClientMetadata().ClientId 41 - // callbackUrl := oauth.GetClientMetadata().RedirectUris[0] 42 - // k, err := oauth.GetJWKS() 43 - // if err != nil { 44 - // h.serverError(w, err) 45 - // return 46 - // } 47 - // cli, err := aog.NewClient(aog.ClientArgs{ 48 - // ClientJwk: *k, 49 - // ClientId: clientID, 50 - // RedirectUri: callbackUrl, 51 - // }) 52 - // if err != nil { 53 - // h.serverError(w, err) 54 - // return 55 - // } 56 - // cli.RefreshTokenRequest 57 - // handle := r.FormValue("handle") 58 - // } 59 - 60 34 func (h *Handler) oauthLogin(w http.ResponseWriter, r *http.Request) { 61 35 err := r.ParseForm() 62 36 if err != nil { 63 37 h.badRequest(w, err) 64 38 return 65 39 } 66 - handle := r.FormValue("handle") 67 - req, res, err := h.oauth.StartAuthFlow(r.Context(), handle) 68 - if err != nil { 69 - h.serverError(w, err) 70 - return 71 - } 72 - err = h.db.StoreOAuthRequest(req, r.Context()) 73 - if err != nil { 74 - h.serverError(w, err) 75 - return 76 - } 77 - u, _ := url.Parse(res.AuthzEndpoint) 78 - u.RawQuery = fmt.Sprintf("client_id=%s&request_uri=%s", url.QueryEscape(oauth.GetClientMetadata().ClientId), res.RequestUri) 79 - 80 - session, _ := h.sessionStore.Get(r, "oauthsession") 81 - session.Values = map[any]any{} 82 - 83 - session.Options = &sessions.Options{ 84 - Path: "/", 85 - MaxAge: 300, 86 - HttpOnly: true, 87 - } 88 - session.Values["oauth_state"] = res.State 89 - session.Values["oauth_did"] = res.DID 90 - err = session.Save(r, w) 40 + identifier := r.FormValue("identifier") 41 + redirectURL, err := h.oauth.StartAuthFlow(r.Context(), identifier) 91 42 if err != nil { 92 43 h.serverError(w, err) 93 44 return 94 45 } 95 - go func() { 96 - err := h.db.StoreDidHandle(res.DID, handle, context.Background()) 97 - h.logger.Deprintln("storing....") 98 - if err != nil { 99 - h.logger.Deprintln("failed to store did handle: " + err.Error()) 100 - } 101 - }() 102 - http.Redirect(w, r, u.String(), http.StatusFound) 46 + http.Redirect(w, r, redirectURL, http.StatusFound) 103 47 } 104 48 105 49 func (h *Handler) oauthCallback(w http.ResponseWriter, r *http.Request) { 106 - resState := r.FormValue("state") 107 - resIss := r.FormValue("iss") 108 - resCode := r.FormValue("code") 109 - session, err := h.sessionStore.Get(r, "oauthsession") 50 + sessData, err := h.oauth.OauthCallback(r.Context(), r.URL.Query()) 51 + err = h.rm.CreateInitialProfile(sessData, r.Context()) 110 52 if err != nil { 111 53 h.serverError(w, err) 112 54 return 113 55 } 114 - if resState == "" || resIss == "" || resCode == "" { 115 - h.badRequest(w, errors.New("did not provide one of resState, resIss, resCode")) 116 - return 117 - } 118 - sessionState, ok := session.Values["oauth_state"].(string) 119 - if !ok { 120 - h.serverError(w, errors.New("oauth_state not found in session")) 121 - return 122 - } 123 - if resState != sessionState { 124 - h.serverError(w, errors.New("resState and sessionState do not match!")) 125 - return 126 - } 127 - params := oauth.CallbackParams{ 128 - State: resState, 129 - Iss: resIss, 130 - Code: resCode, 131 - } 132 - req, err := h.db.GetOauthRequest(resState, r.Context()) 133 - if err != nil { 134 - h.serverError(w, err) 135 - return 136 - } 137 - OauthSession, err := h.oauth.OauthCallback(r.Context(), req, params) 138 - if err != nil { 139 - h.serverError(w, err) 140 - return 141 - } 142 - err = h.db.DeleteOauthRequest(resState, r.Context()) 143 - if err != nil { 144 - h.serverError(w, err) 145 - return 146 - } 147 - h.logger.Deprintf("id: %d %d", OauthSession.ID, req.ID) 148 - err = h.db.StoreOAuthSession(OauthSession, r.Context()) 149 - if err != nil { 150 - h.serverError(w, err) 151 - return 152 - } 153 - err = h.rm.CreateInitialProfile(req.Did, req.ID, r.Context()) 56 + session, _ := h.sessionStore.Get(r, "oauthsession") 154 57 if err != nil { 155 58 h.serverError(w, err) 156 59 return ··· 162 65 HttpOnly: true, 163 66 } 164 67 session.Values = map[any]any{} 165 - session.Values["did"] = req.Did 166 - session.Values["id"] = req.ID 68 + session.Values["did"] = sessData.AccountDID.String() 69 + session.Values["id"] = sessData.SessionID 70 + session.Values["scopes"] = strings.Join(sessData.Scopes, " ") 167 71 err = session.Save(r, w) 168 72 if err != nil { 169 73 h.serverError(w, err) ··· 226 130 227 131 func (h *Handler) oauthLogout(w http.ResponseWriter, r *http.Request) { 228 132 s, _ := h.sessionStore.Get(r, "oauthsession") 229 - id, ok := s.Values["id"].(int) 230 - if ok { 133 + id, ok := s.Values["id"].(string) 134 + did, bok := s.Values["did"].(string) 135 + if ok && bok { 231 136 h.logger.Deprintln("deleting session to log out!") 232 - err := h.rm.DeleteSession(id, r.Context()) 137 + err := h.rm.DeleteSession(did, id, r.Context()) 233 138 if err != nil { 234 139 h.serverError(w, errors.New("couldn't log out: "+err.Error())) 235 140 return 236 141 } 237 142 h.logger.Deprintln("deleted session to log out!") 238 143 } 239 - s.Values = make(map[interface{}]interface{}) 144 + s.Values = make(map[any]any) 240 145 s.Options.MaxAge = -1 241 146 h.logger.Deprintln("saving cookie to log out!") 242 147 err := s.Save(r, w)
+5 -4
server/internal/handler/xcvrHandlers.go
··· 21 21 return 22 22 } 23 23 s, _ := h.sessionStore.Get(r, "oauthsession") 24 - id, ok := s.Values["id"].(int) 24 + id, ok := s.Values["id"].(string) 25 25 if !ok { 26 26 h.badRequest(w, errors.New("must be logged in!")) 27 27 return ··· 35 35 36 36 func (h *Handler) beep(w http.ResponseWriter, r *http.Request) { 37 37 s, _ := h.sessionStore.Get(r, "oauthsession") 38 - id, ok := s.Values["id"].(int) 39 - if !ok { 38 + id, ok := s.Values["id"].(string) 39 + did, bok := s.Values["did"].(string) 40 + if !ok || !bok { 40 41 h.badRequest(w, errors.New("must be logged in!")) 41 42 return 42 43 } 43 - err := h.rm.Beep(id, r.Context()) 44 + err := h.rm.Beep(did, id, r.Context()) 44 45 if err != nil { 45 46 h.badRequest(w, err) 46 47 return
-98
server/internal/oauth/clientmapper.go
··· 1 - package oauth 2 - 3 - import ( 4 - "context" 5 - "errors" 6 - "sync" 7 - "time" 8 - ) 9 - 10 - type ClientMap struct { 11 - svc *Service 12 - clients map[int]*OauthXRPCClient 13 - expiry map[int]time.Time 14 - texp map[int]time.Time 15 - mu sync.Mutex 16 - } 17 - 18 - func NewClientMap(service *Service) *ClientMap { 19 - return &ClientMap{ 20 - svc: service, 21 - clients: make(map[int]*OauthXRPCClient, 10), 22 - expiry: make(map[int]time.Time, 10), 23 - texp: make(map[int]time.Time, 10), 24 - mu: sync.Mutex{}, 25 - } 26 - } 27 - 28 - func (c *ClientMap) Map(id int, ctx context.Context) (cli *OauthXRPCClient, refreshed bool, err error) { 29 - c.mu.Lock() 30 - defer c.mu.Unlock() 31 - cli = c.clients[id] 32 - if cli == nil { 33 - return 34 - } 35 - 36 - texp := c.texp[id] 37 - expiry := c.expiry[id] 38 - if time.Now().After(expiry) { 39 - c.Delete(id) 40 - err = errors.New("client has expired") 41 - return 42 - } 43 - if texp.Sub(time.Now()) <= 5*time.Minute { 44 - var newexp time.Time 45 - newexp, err = c.svc.RefreshToken(ctx, cli.session) 46 - if err != nil { 47 - err = errors.New("failed to refresh expired token: " + err.Error()) 48 - return 49 - } 50 - refreshed = true 51 - c.texp[id] = newexp 52 - } 53 - return 54 - } 55 - 56 - func (c *ClientMap) Append(id int, client *OauthXRPCClient, expiration time.Time) { 57 - c.mu.Lock() 58 - defer c.mu.Unlock() 59 - c.clients[id] = client 60 - c.expiry[id] = expiration 61 - c.texp[id] = time.Now() 62 - 63 - } 64 - 65 - func (c *ClientMap) Cleanup() { 66 - now := time.Now() 67 - c.mu.Lock() 68 - defer c.mu.Unlock() 69 - for id, client := range c.clients { 70 - expiry, ok := c.expiry[id] 71 - if !ok { 72 - delete(c.expiry, id) 73 - delete(c.clients, id) 74 - delete(c.texp, id) 75 - continue 76 - } 77 - if client == nil { 78 - delete(c.expiry, id) 79 - delete(c.clients, id) 80 - delete(c.texp, id) 81 - continue 82 - } 83 - if now.After(expiry) { 84 - delete(c.expiry, id) 85 - delete(c.clients, id) 86 - delete(c.texp, id) 87 - continue 88 - } 89 - } 90 - } 91 - 92 - func (c *ClientMap) Delete(id int) { 93 - c.mu.Lock() 94 - defer c.mu.Unlock() 95 - delete(c.clients, id) 96 - delete(c.expiry, id) 97 - delete(c.texp, id) 98 - }
+4 -16
server/internal/oauth/jwks.go
··· 1 1 package oauth 2 2 3 3 import ( 4 + "github.com/bluesky-social/indigo/atproto/crypto" 4 5 "os" 5 - "github.com/haileyok/atproto-oauth-golang/helpers" 6 - "github.com/lestrrat-go/jwx/v2/jwk" 7 6 ) 8 7 9 - var ( 10 - key *jwk.Key 11 - ) 12 - 13 - func GetJWKS() (*jwk.Key, error) { 14 - if key != nil { 15 - return key, nil 16 - } 17 - b, err := os.ReadFile("../jwks.json") 18 - if err != nil { 19 - return nil, err 20 - } 21 - k, err := helpers.ParseJWKFromBytes(b) 8 + func GetPrivateKey() (*crypto.PrivateKeyK256, error) { 9 + csk := os.Getenv("CLIENT_SECRET_KEY") 10 + key, err := crypto.ParsePrivateBytesK256([]byte(csk)) 22 11 if err != nil { 23 12 return nil, err 24 13 } 25 - key = &k 26 14 return key, nil 27 15 }
+69 -118
server/internal/oauth/oauthclient.go
··· 5 5 "encoding/json" 6 6 "errors" 7 7 "github.com/bluesky-social/indigo/api/atproto" 8 - "github.com/bluesky-social/indigo/api/bsky" 9 - "github.com/bluesky-social/indigo/atproto/client" 8 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 10 9 "github.com/bluesky-social/indigo/atproto/syntax" 11 - "github.com/bluesky-social/indigo/lex/util" 12 - "github.com/haileyok/atproto-oauth-golang" 13 - "github.com/haileyok/atproto-oauth-golang/helpers" 14 10 15 - "rvcx/internal/db" 16 11 "rvcx/internal/lex" 17 12 "rvcx/internal/log" 18 13 "rvcx/internal/types" 19 14 ) 20 15 21 16 type OauthXRPCClient struct { 22 - xrpc *oauth.XrpcClient 23 17 session *types.Session 24 18 logger *log.Logger 25 19 } 26 20 27 - func NewOauthXRPCClient(s *db.Store, l *log.Logger, session *types.Session) *OauthXRPCClient { 28 - return &OauthXRPCClient{ 29 - xrpc: &oauth.XrpcClient{ 30 - OnDpopPdsNonceChanged: func(did, newNonce string) { 31 - err := s.SetDpopPdsNonce(session.ID, newNonce) 32 - if err != nil { 33 - l.Println(err.Error()) 34 - return 35 - } 36 - session.DpopPdsNonce = newNonce 37 - }, 38 - }, 39 - session: session, 40 - logger: l, 41 - } 42 - } 43 - 44 21 func (c *OauthXRPCClient) GetSession() *types.Session { 45 22 return c.session 46 23 } 47 24 48 - func (c *OauthXRPCClient) getOauthSessionAuthArgs() (*oauth.XrpcAuthedRequestArgs, error) { 49 - s := c.session 50 - privateJwk, err := helpers.ParseJWKFromBytes([]byte(s.DpopPrivKey)) 51 - if err != nil { 52 - return nil, errors.New("failed to parse jwk in getoauthsessionauthargs: " + err.Error()) 53 - } 54 - return &oauth.XrpcAuthedRequestArgs{ 55 - Did: s.Did, 56 - AccessToken: s.AccessToken, 57 - PdsUrl: s.PdsUrl, 58 - Issuer: s.AuthserverIss, 59 - DpopPdsNonce: s.DpopPdsNonce, 60 - DpopPrivateJwk: privateJwk, 61 - }, nil 62 - } 63 - 64 - func (c *OauthXRPCClient) MakeBskyPost(text string, ctx context.Context) error { 65 - authargs, err := c.getOauthSessionAuthArgs() 66 - if err != nil { 67 - return errors.New("failed to get oauthsessionauthargs while making post: " + err.Error()) 68 - } 69 - post := bsky.FeedPost{ 70 - Text: text, 71 - CreatedAt: syntax.DatetimeNow().String(), 72 - } 73 - input := atproto.RepoCreateRecord_Input{ 74 - Collection: "app.bsky.feed.post", 75 - Repo: authargs.Did, 76 - Record: &util.LexiconTypeDecoder{Val: &post}, 25 + func MakeBskyPost(cs *oauth.ClientSession, text string, ctx context.Context) error { 26 + c := cs.APIClient() 27 + body := map[string]any{ 28 + "repo": *c.AccountDID, 29 + "collection": "app.bsky.feed.post", 30 + "record": map[string]any{ 31 + "$type": "app.bsky.feed.post", 32 + "text": text, 33 + "createdAt": syntax.DatetimeNow(), 34 + }, 77 35 } 78 - var out atproto.RepoCreateRecord_Output 79 - err = c.xrpc.Do(ctx, authargs, "POST", "application/json", "com.atproto.repo.createRecord", nil, input, &out) 36 + err := c.Post(ctx, "com.atproto.repo.createRecord", body, nil) 80 37 if err != nil { 81 - return errors.New("oops! failed to make post: " + err.Error()) 38 + return errors.New("failed to tweet: " + err.Error()) 82 39 } 83 40 return nil 84 41 } 85 42 86 - func (c *OauthXRPCClient) CreateXCVRProfile(profile *lex.ProfileRecord, ctx context.Context) (p *lex.ProfileRecord, err error) { 87 - authargs, err := c.getOauthSessionAuthArgs() 43 + func CreateXCVRProfile(cs *oauth.ClientSession, profile *lex.ProfileRecord, ctx context.Context) (p *lex.ProfileRecord, err error) { 44 + c := cs.APIClient() 45 + nsid, err := syntax.ParseNSID("com.atproto.repo.getRecord") 88 46 if err != nil { 89 - err = errors.New("failed to get oauthsessionauthargs while making post: " + err.Error()) 90 - return 47 + return nil, errors.New("failed to parse: " + err.Error()) 91 48 } 92 - getOut, err := getProfileRecord(authargs.PdsUrl, authargs.Did, ctx) 49 + var getOut atproto.RepoGetRecord_Output 50 + err = c.Get(ctx, nsid, nil, &getOut) 93 51 if err == nil { 94 52 if getOut.Cid != nil { 95 53 var jsonBytes []byte ··· 105 63 return &pro, nil 106 64 } 107 65 } 108 - rkey := "self" 109 - input := atproto.RepoCreateRecord_Input{ 110 - Collection: "org.xcvr.actor.profile", 111 - Repo: authargs.Did, 112 - Rkey: &rkey, 113 - Record: &util.LexiconTypeDecoder{Val: profile}, 66 + body := map[string]any{ 67 + "collection": "org.xcvr.actor.profile", 68 + "repo": *c.AccountDID, 69 + "rkey": "self", 70 + "record": profile, 114 71 } 115 72 var out atproto.RepoCreateRecord_Output 116 - err = c.xrpc.Do(ctx, authargs, "POST", "application/json", "com.atproto.repo.createRecord", nil, input, &out) 73 + err = c.Post(ctx, "com.atproto.repo.createRecord", body, out) 117 74 if err != nil { 118 75 err = errors.New("oops! failed to create a profile: " + err.Error()) 119 76 return ··· 121 78 return profile, nil 122 79 } 123 80 124 - func (c *OauthXRPCClient) CreateXCVRChannel(channel *lex.ChannelRecord, ctx context.Context) (uri string, cid string, err error) { 125 - authargs, err := c.getOauthSessionAuthArgs() 126 - if err != nil { 127 - err = errors.New("yikers! couldn't createXCVRChannel: " + err.Error()) 128 - return 129 - } 130 - input := atproto.RepoCreateRecord_Input{ 131 - Collection: "org.xcvr.feed.channel", 132 - Repo: authargs.Did, 133 - Record: &util.LexiconTypeDecoder{Val: channel}, 81 + func CreateXCVRChannel(cs *oauth.ClientSession, channel *lex.ChannelRecord, ctx context.Context) (uri string, cid string, err error) { 82 + c := cs.APIClient() 83 + body := map[string]any{ 84 + "collection": "org.xcvr.actor.profile", 85 + "repo": *c.AccountDID, 86 + "record": channel, 134 87 } 135 88 var out atproto.RepoCreateRecord_Output 136 - err = c.xrpc.Do(ctx, authargs, "POST", "application/json", "com.atproto.repo.createRecord", nil, input, &out) 89 + err = c.Post(ctx, "com.atproto.repo.createRecord", body, out) 137 90 if err != nil { 138 - err = errors.New("that's not good! failed to create a XCVRChannel: " + err.Error()) 91 + err = errors.New("oops! failed to create a profile: " + err.Error()) 139 92 return 140 93 } 141 94 uri = out.Uri ··· 143 96 return 144 97 } 145 98 146 - func (c *OauthXRPCClient) CreateXCVRMessage(message *lex.MessageRecord, ctx context.Context) (uri string, cid string, err error) { 147 - authargs, err := c.getOauthSessionAuthArgs() 148 - if err != nil { 149 - err = errors.New("uh oh... I couldn't make a XCVRMessage: " + err.Error()) 150 - return 151 - } 152 - input := atproto.RepoCreateRecord_Input{ 153 - Collection: "org.xcvr.lrc.message", 154 - Repo: authargs.Did, 155 - Record: &util.LexiconTypeDecoder{Val: message}, 99 + func CreateXCVRMessage(cs *oauth.ClientSession, message *lex.MessageRecord, ctx context.Context) (uri string, cid string, err error) { 100 + c := cs.APIClient() 101 + body := map[string]any{ 102 + "collection": "org.xcvr.actor.profile", 103 + "repo": *c.AccountDID, 104 + "record": message, 156 105 } 157 106 var out atproto.RepoCreateRecord_Output 158 - err = c.xrpc.Do(ctx, authargs, "POST", "application/json", "com.atproto.repo.createRecord", nil, input, &out) 107 + err = c.Post(ctx, "com.atproto.repo.createRecord", body, out) 159 108 if err != nil { 160 - err = errors.New("i've got a bad feeling aobut this... failed to create XCVRMessage: " + err.Error()) 109 + err = errors.New("oops! failed to create a message: " + err.Error()) 161 110 return 162 111 } 163 112 uri = out.Uri ··· 165 114 return 166 115 } 167 116 168 - func (c *OauthXRPCClient) UpdateXCVRProfile(profile *lex.ProfileRecord, ctx context.Context) (p *lex.ProfileRecord, err error) { 169 - authargs, err := c.getOauthSessionAuthArgs() 117 + func UpdateXCVRProfile(cs *oauth.ClientSession, profile *lex.ProfileRecord, ctx context.Context) (p *lex.ProfileRecord, err error) { 118 + c := cs.APIClient() 119 + nsid, err := syntax.ParseNSID("com.atproto.repo.getRecord") 170 120 if err != nil { 171 - err = errors.New("failed to get oauthsessionauthargs while making post: " + err.Error()) 172 - return 121 + return nil, errors.New("failed to parse: " + err.Error()) 173 122 } 174 - getOut, err := getProfileRecord(authargs.PdsUrl, authargs.Did, ctx) 175 - if err != nil { 176 - err = errors.New("messed that up! " + err.Error()) 177 - return 178 - } 179 - if getOut.Cid == nil { 180 - return c.CreateXCVRProfile(profile, ctx) 123 + var getOut atproto.RepoGetRecord_Output 124 + err = c.Get(ctx, nsid, nil, &getOut) 125 + if err == nil { 126 + if getOut.Cid != nil { 127 + var jsonBytes []byte 128 + jsonBytes, err = json.Marshal(getOut.Value) 129 + if err != nil { 130 + return 131 + } 132 + var pro lex.ProfileRecord 133 + err = json.Unmarshal(jsonBytes, &pro) 134 + if err != nil { 135 + return 136 + } 137 + return &pro, nil 138 + } 181 139 } 182 - rkey := "self" 183 - input := atproto.RepoPutRecord_Input{ 184 - Collection: "org.xcvr.actor.profile", 185 - Repo: authargs.Did, 186 - Rkey: rkey, 187 - Record: &util.LexiconTypeDecoder{Val: profile}, 188 - SwapRecord: getOut.Cid, 140 + body := map[string]any{ 141 + "collection": "org.xcvr.actor.profile", 142 + "repo": *c.AccountDID, 143 + "rkey": "self", 144 + "record": profile, 189 145 } 190 - var out atproto.RepoPutRecord_Output 191 - err = c.xrpc.Do(ctx, authargs, "POST", "application/json", "com.atproto.repo.putRecord", nil, input, &out) 146 + var out atproto.RepoCreateRecord_Output 147 + err = c.Post(ctx, "com.atproto.repo.createRecord", body, out) 192 148 if err != nil { 193 - err = errors.New("oops! failed to update a profile: " + err.Error()) 149 + err = errors.New("oops! failed to create a profile: " + err.Error()) 194 150 return 195 151 } 196 152 return profile, nil 197 153 } 198 - 199 - func getProfileRecord(pdsUrl string, did string, ctx context.Context) (*atproto.RepoGetRecord_Output, error) { 200 - cli := client.NewAPIClient(pdsUrl) 201 - return atproto.RepoGetRecord(ctx, cli, "", "org.xcvr.actor.profile", did, "self") 202 - }
+17 -156
server/internal/oauth/service.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "encoding/json" 6 - "errors" 7 - "fmt" 8 - "net/http" 9 - "rvcx/internal/atputils" 10 - "rvcx/internal/types" 11 - "time" 5 + "net/url" 6 + "rvcx/internal/db" 12 7 13 - atoauth "github.com/haileyok/atproto-oauth-golang" 14 - "github.com/haileyok/atproto-oauth-golang/helpers" 15 - "github.com/lestrrat-go/jwx/v2/jwk" 8 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 9 + "github.com/bluesky-social/indigo/atproto/syntax" 16 10 ) 17 11 18 12 type Service struct { 19 - oauth *atoauth.Client 20 - http *http.Client 21 - keys *jwk.Key 13 + app *oauth.ClientApp 22 14 } 23 15 24 - func NewService(httpClient *http.Client) (*Service, error) { 25 - key, err := GetJWKS() 26 - if err != nil { 27 - return nil, err 28 - } 29 - cid := getClientId() 30 - cbu := getOauthCallback() 31 - cli, err := atoauth.NewClient(atoauth.ClientArgs{ 32 - ClientJwk: *key, 33 - ClientId: cid, 34 - RedirectUri: cbu, 35 - }) 16 + func NewService(store db.Store) (*Service, error) { 17 + config := oauth.NewPublicConfig(getClientId(), getOauthCallback(), []string{"atproto", "transition:generic"}) 18 + key, err := GetPrivateKey() 36 19 if err != nil { 37 20 return nil, err 38 21 } 39 - return &Service{ 40 - oauth: cli, 41 - http: httpClient, 42 - keys: key, 43 - }, nil 22 + err = config.SetClientSecret(key, "secret.key.name_") 23 + app := oauth.NewClientApp(&config, store) 24 + return &Service{app}, nil 44 25 } 45 26 46 - type CallbackParams struct { 47 - Iss string 48 - State string 49 - Code string 27 + func (s *Service) StartAuthFlow(ctx context.Context, identifier string) (redirectURL string, err error) { 28 + return s.app.StartAuthFlow(ctx, identifier) 50 29 } 51 30 52 - func (s *Service) StartAuthFlow(ctx context.Context, handle string) (*types.OAuthRequest, *types.OauthFlowResult, error) { 53 - did, err := atputils.GetDidFromHandle(ctx, handle) 54 - if err != nil { 55 - return nil, nil, errors.New("error resolving handle:" + err.Error()) 56 - } 57 - dpopPrivKey, err := helpers.GenerateKey(nil) 58 - if err != nil { 59 - return nil, nil, errors.New("error generating key:" + err.Error()) 60 - } 61 - dpopPrivKeyJson, err := json.Marshal(dpopPrivKey) 62 - if err != nil { 63 - return nil, nil, errors.New("error marshaling privkey to json:" + err.Error()) 64 - } 65 - parResp, metadata, service, err := s.makeOAuthRequest(ctx, did, handle, dpopPrivKey) 66 - if err != nil { 67 - return nil, nil, errors.New("error making oauth request:" + err.Error()) 68 - } 69 - oauthReq := types.OAuthRequest{ 70 - AuthserverIss: metadata.Issuer, 71 - State: parResp.State, 72 - Did: did, 73 - PkceVerifier: parResp.PkceVerifier, 74 - DpopAuthServerNonce: parResp.DpopAuthserverNonce, 75 - DpopPrivKey: string(dpopPrivKeyJson), 76 - PdsUrl: service, 77 - } 78 - oauthFlowResult := types.OauthFlowResult{ 79 - AuthzEndpoint: metadata.AuthorizationEndpoint, 80 - State: parResp.State, 81 - DID: did, 82 - RequestUri: parResp.RequestUri, 83 - } 84 - return &oauthReq, &oauthFlowResult, nil 85 - 31 + func (s *Service) OauthCallback(ctx context.Context, params url.Values) (sessdata *oauth.ClientSessionData, err error) { 32 + return s.app.ProcessCallback(ctx, params) 86 33 } 87 34 88 - func (s *Service) makeOAuthRequest(ctx context.Context, did string, handle string, dpop jwk.Key) (resp *atoauth.SendParAuthResponse, meta *atoauth.OauthAuthorizationMetadata, service string, err error) { 89 - service, err = s.resolveService(ctx, did) 90 - if err != nil { 91 - err = errors.New("error resolving service:" + err.Error()) 92 - return 93 - } 94 - authserver, err := s.oauth.ResolvePdsAuthServer(ctx, service) 95 - if err != nil { 96 - err = errors.New("error resolving pds service:" + err.Error()) 97 - return 98 - } 99 - meta, err = s.oauth.FetchAuthServerMetadata(ctx, authserver) 100 - if err != nil { 101 - err = errors.New("error fetching " + authserver + " metadata:" + err.Error()) 102 - return 103 - } 104 - resp, err = s.oauth.SendParAuthRequest(ctx, authserver, meta, handle, "atproto transition:generic", dpop) 105 - if err != nil { 106 - err = errors.New("error sending PAR auth request to " + authserver + " h: " + handle + err.Error()) 107 - } 108 - return 109 - } 110 - 111 - func (s *Service) resolveService(ctx context.Context, did string) (string, error) { 112 - return atputils.GetPDSFromDid(ctx, did, s.http) 113 - } 114 - 115 - // func (s *Service) resolveHandle(handle string) (string, error) { 116 - // params := url.Values{ 117 - // "handle": []string{handle}, 118 - // } 119 - // reqUrl := "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?" + params.Encode() 120 - // resp, err := s.http.Get(reqUrl) 121 - // if err != nil { 122 - // return "", errors.New("error making handle -> did resolution request:" + err.Error()) 123 - // } 124 - // defer resp.Body.Close() 125 - // 126 - // type did struct { 127 - // Did string 128 - // } 129 - // b, err := io.ReadAll(resp.Body) 130 - // if err != nil { 131 - // return "", errors.New("error reading handle -> did resolution response" + err.Error()) 132 - // } 133 - // var resDid did 134 - // err = json.Unmarshal(b, &resDid) 135 - // if err != nil { 136 - // return "", errors.New("error unmarshaling resDid:" + err.Error()) 137 - // } 138 - // return resDid.Did, nil 139 - // } 140 - 141 - func (s *Service) OauthCallback(ctx context.Context, oauthRequest *types.OAuthRequest, params CallbackParams) (*types.Session, error) { 142 - jwk, err := helpers.ParseJWKFromBytes([]byte(oauthRequest.DpopPrivKey)) 143 - if err != nil { 144 - return nil, errors.New("error parsing jwk:" + err.Error()) 145 - } 146 - initialTokenResp, err := s.oauth.InitialTokenRequest(ctx, params.Code, params.Iss, oauthRequest.PkceVerifier, oauthRequest.DpopAuthServerNonce, jwk) 147 - if err != nil { 148 - return nil, errors.New("error in initialTokenRequest:" + err.Error()) 149 - } 150 - if initialTokenResp.Scope != "atproto transition:generic" { 151 - return nil, errors.New(fmt.Sprintf("incorrect scope: %s", initialTokenResp.Scope)) 152 - } 153 - oauthSession := types.Session{ 154 - OAuthRequest: *oauthRequest, 155 - AccessToken: initialTokenResp.AccessToken, 156 - RefreshToken: initialTokenResp.RefreshToken, 157 - Expiration: time.Now().Add(time.Duration(int(time.Second) * int(initialTokenResp.ExpiresIn))), 158 - } 159 - return &oauthSession, nil 160 - } 161 - 162 - func (s *Service) RefreshToken(ctx context.Context, session *types.Session) (newexpiry time.Time, err error) { 163 - jwk, err := helpers.ParseJWKFromBytes([]byte(session.DpopPrivKey)) 164 - if err != nil { 165 - return 166 - } 167 - resp, err := s.oauth.RefreshTokenRequest(ctx, session.RefreshToken, session.AuthserverIss, session.DpopAuthServerNonce, jwk) 168 - if err != nil { 169 - return 170 - } 171 - session.AccessToken = resp.AccessToken 172 - session.DpopAuthServerNonce = resp.DpopAuthserverNonce 173 - session.RefreshToken = resp.RefreshToken 174 - newexpiry = time.Now().Add(1 * time.Hour) 175 - return 35 + func (s *Service) ResumeSession(ctx context.Context, did syntax.DID, sessionId string) (sess *oauth.ClientSession, err error) { 36 + return s.app.ResumeSession(ctx, did, sessionId) 176 37 }
+10 -4
server/internal/recordmanager/beep.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "errors" 6 + "github.com/bluesky-social/indigo/atproto/syntax" 7 + "rvcx/internal/oauth" 5 8 ) 6 9 7 - func (rm *RecordManager) Beep(id int, ctx context.Context) error { 8 - 9 - client, err := rm.getClient(id, ctx) 10 + func (rm *RecordManager) Beep(id string, did string, ctx context.Context) error { 11 + sdid, err := syntax.ParseDID(did) 12 + if err != nil { 13 + return errors.New("aaaa bbeeeebpp : " + err.Error()) 14 + } 15 + client, err := rm.service.ResumeSession(ctx, sdid, id) 10 16 if err != nil { 11 17 return err 12 18 } 13 - return client.MakeBskyPost("beep_", ctx) 19 + return oauth.MakeBskyPost(client, "beep_", ctx) 14 20 }
+10 -5
server/internal/recordmanager/channel.go
··· 6 6 "github.com/bluesky-social/indigo/atproto/syntax" 7 7 "rvcx/internal/atputils" 8 8 "rvcx/internal/lex" 9 + "rvcx/internal/oauth" 9 10 "rvcx/internal/types" 10 11 "time" 11 12 ) ··· 46 47 return rm.postchannelflow(rm.createMyChannel(), ctx, pcr) 47 48 } 48 49 49 - func (rm *RecordManager) PostChannel(id int, udid string, ctx context.Context, pcr *types.PostChannelRequest) (did string, uri string, err error) { 50 - return rm.postchannelflow(rm.createChannel(id, udid), ctx, pcr) 50 + func (rm *RecordManager) PostChannel(sessionId string, udid string, ctx context.Context, pcr *types.PostChannelRequest) (did string, uri string, err error) { 51 + return rm.postchannelflow(rm.createChannel(sessionId, udid), ctx, pcr) 51 52 } 52 53 53 54 func (rm *RecordManager) postchannelflow(f func(*lex.ChannelRecord, *time.Time, context.Context) (*types.Channel, error), ctx context.Context, pcr *types.PostChannelRequest) (did string, uri string, err error) { ··· 92 93 return rm.broadcaster.UpdateChannel(c) 93 94 } 94 95 95 - func (rm *RecordManager) createChannel(id int, did string) func(*lex.ChannelRecord, *time.Time, context.Context) (*types.Channel, error) { 96 + func (rm *RecordManager) createChannel(sessionId string, did string) func(*lex.ChannelRecord, *time.Time, context.Context) (*types.Channel, error) { 96 97 return func(lcr *lex.ChannelRecord, now *time.Time, ctx context.Context) (*types.Channel, error) { 97 - client, err := rm.getClient(id, ctx) 98 + sdid, err := syntax.ParseDID(did) 99 + if err != nil { 100 + return nil, err 101 + } 102 + client, err := rm.service.ResumeSession(ctx, sdid, sessionId) 98 103 if err != nil { 99 104 return nil, errors.New("couldn't get client") 100 105 } 101 - uri, cid, err := client.CreateXCVRChannel(lcr, ctx) 106 + uri, cid, err := oauth.CreateXCVRChannel(client, lcr, ctx) 102 107 if err != nil { 103 108 return nil, errors.New("something bad probs happened when posting a channel " + err.Error()) 104 109 }
+10 -5
server/internal/recordmanager/message.go
··· 8 8 "os" 9 9 "rvcx/internal/atputils" 10 10 "rvcx/internal/lex" 11 + "rvcx/internal/oauth" 11 12 "rvcx/internal/types" 12 13 "slices" 13 14 "time" ··· 68 69 return nil 69 70 } 70 71 71 - func (rm *RecordManager) PostMessage(id int, udid string, ctx context.Context, pmr *types.PostMessageRequest) error { 72 + func (rm *RecordManager) PostMessage(sessionId string, udid string, ctx context.Context, pmr *types.PostMessageRequest) error { 72 73 rm.log.Deprintln("validate") 73 74 lmr, now, _, _, err := rm.validateMessage(pmr, ctx) 74 75 if err != nil { 75 76 return errors.New("failed to validate message: " + err.Error()) 76 77 } 77 78 rm.log.Deprintln("create") 78 - m, err := rm.createMessage(id, udid, lmr, now, ctx) 79 + m, err := rm.createMessage(udid, sessionId, lmr, now, ctx) 79 80 if err != nil { 80 81 return errors.New("failed to create message: " + err.Error()) 81 82 } ··· 154 155 return message, nil 155 156 } 156 157 157 - func (rm *RecordManager) createMessage(id int, did string, lmr *lex.MessageRecord, now *time.Time, ctx context.Context) (*types.Message, error) { 158 - client, err := rm.getClient(id, ctx) 158 + func (rm *RecordManager) createMessage(did string, sessionID string, lmr *lex.MessageRecord, now *time.Time, ctx context.Context) (*types.Message, error) { 159 + sdid, err := syntax.ParseDID(did) 160 + if err != nil { 161 + return nil, errors.New(" error: " + err.Error()) 162 + } 163 + client, err := rm.service.ResumeSession(ctx, sdid, sessionID) 159 164 if err != nil { 160 165 return nil, errors.New("failed to get client: " + err.Error()) 161 166 } 162 - uri, cid, err := client.CreateXCVRMessage(lmr, ctx) 167 + uri, cid, err := oauth.CreateXCVRMessage(client, lmr, ctx) 163 168 if err != nil { 164 169 return nil, errors.New("couldn't add to user repo: " + err.Error()) 165 170 }
+23 -15
server/internal/recordmanager/profile.go
··· 6 6 "rvcx/internal/atputils" 7 7 "rvcx/internal/db" 8 8 "rvcx/internal/lex" 9 + "rvcx/internal/oauth" 9 10 "rvcx/internal/types" 11 + 12 + atoauth "github.com/bluesky-social/indigo/atproto/auth/oauth" 13 + "github.com/bluesky-social/indigo/atproto/syntax" 10 14 ) 11 15 12 16 func (rm *RecordManager) AcceptProfile(p lex.ProfileRecord, did string, ctx context.Context) error { ··· 47 51 } 48 52 } 49 53 50 - func (rm *RecordManager) CreateInitialProfile(did string, id int, ctx context.Context) error { 54 + func (rm *RecordManager) CreateInitialProfile(sessData *atoauth.ClientSessionData, ctx context.Context) error { 51 55 nick := "wanderer" 52 56 status := "just setting up my xcvr" 53 57 color := uint64(3702605) 54 - handle, err := rm.db.ResolveDid(did, ctx) 58 + handle, err := rm.db.ResolveDid(sessData.AccountDID.String(), ctx) 55 59 if err != nil { 56 60 return errors.New("i couldn't find the handle, so i couldn't create default profile record. gootbye") 57 61 } 58 62 59 - p, err := rm.createProfile(&handle, &nick, &status, &color, id, ctx) 63 + p, err := rm.createProfile(&handle, &nick, &status, &color, sessData, ctx) 60 64 if err != nil { 61 65 return errors.New("AAAAA error creating profile" + err.Error()) 62 66 } 63 67 rm.log.Deprintln("initializing profile....") 64 - err = rm.db.InitializeProfile(did, p.DisplayName, p.DefaultNick, p.Status, p.Color, ctx) 68 + err = rm.db.InitializeProfile(sessData.AccountDID.String(), p.DisplayName, p.DefaultNick, p.Status, p.Color, ctx) 65 69 if err != nil { 66 70 return errors.New("failed to initialize profile: " + err.Error()) 67 71 } ··· 69 73 70 74 } 71 75 72 - func (rm *RecordManager) PostProfile(did string, id int, ctx context.Context, p *types.PostProfileRequest) error { 76 + func (rm *RecordManager) PostProfile(did string, sessionID string, ctx context.Context, p *types.PostProfileRequest) error { 77 + sdid, err := syntax.ParseDID(did) 78 + if err != nil { 79 + return errors.New("bad: " + err.Error()) 80 + } 73 81 pu, err := rm.validateProfile(did, p) 74 82 if err != nil { 75 83 return errors.New("couldn't validate profile: " + err.Error()) 76 84 } 77 - err = rm.updateProfile(p.DisplayName, p.DefaultNick, p.Status, p.Color, id, ctx) 85 + cs, err := rm.service.ResumeSession(ctx, sdid, sessionID) 86 + if err != nil { 87 + return errors.New("couldn't resume session: " + err.Error()) 88 + } 89 + err = rm.updateProfile(cs, p.DisplayName, p.DefaultNick, p.Status, p.Color, ctx) 78 90 if err != nil { 79 91 return errors.New("couldn't create profile: " + err.Error()) 80 92 } ··· 93 105 return nil 94 106 } 95 107 96 - func (rm *RecordManager) updateProfile(name *string, nick *string, status *string, color *uint64, id int, ctx context.Context) error { 108 + func (rm *RecordManager) updateProfile(cs *atoauth.ClientSession, name *string, nick *string, status *string, color *uint64, ctx context.Context) error { 97 109 profilerecord := &lex.ProfileRecord{ 98 110 DisplayName: name, 99 111 DefaultNick: nick, 100 112 Status: status, 101 113 Color: color, 102 114 } 103 - client, err := rm.getClient(id, ctx) 104 - if err != nil { 105 - return err 106 - } 107 - _, err = client.UpdateXCVRProfile(profilerecord, ctx) 115 + _, err := oauth.UpdateXCVRProfile(cs, profilerecord, ctx) 108 116 if err != nil { 109 117 return err 110 118 } 111 119 return nil 112 120 } 113 121 114 - func (rm *RecordManager) createProfile(name *string, nick *string, status *string, color *uint64, id int, ctx context.Context) (*lex.ProfileRecord, error) { 122 + func (rm *RecordManager) createProfile(name *string, nick *string, status *string, color *uint64, sessData *atoauth.ClientSessionData, ctx context.Context) (*lex.ProfileRecord, error) { 115 123 profilerecord := &lex.ProfileRecord{ 116 124 DisplayName: name, 117 125 DefaultNick: nick, 118 126 Status: status, 119 127 Color: color, 120 128 } 121 - client, err := rm.getClient(id, ctx) 129 + client, err := rm.service.ResumeSession(ctx, sessData.AccountDID, sessData.SessionID) 122 130 if err != nil { 123 131 return nil, err 124 132 } 125 - p, err := client.CreateXCVRProfile(profilerecord, ctx) 133 + p, err := oauth.CreateXCVRProfile(client, profilerecord, ctx) 126 134 if err != nil { 127 135 return nil, errors.New("failed to create profile: " + err.Error()) 128 136 }
+2 -50
server/internal/recordmanager/recordmanager.go
··· 1 1 package recordmanager 2 2 3 3 import ( 4 - "context" 5 - "errors" 6 - "fmt" 7 4 "rvcx/internal/db" 8 5 "rvcx/internal/log" 9 6 "rvcx/internal/oauth" ··· 22 19 log *log.Logger 23 20 db *db.Store 24 21 myClient *oauth.PasswordClient 25 - clientmap *oauth.ClientMap 22 + service *oauth.Service 26 23 broadcaster LexBroadcaster 27 24 } 28 25 29 26 func New(log *log.Logger, db *db.Store, myClient *oauth.PasswordClient, service *oauth.Service) *RecordManager { 30 - clientmap := oauth.NewClientMap(service) 31 - return &RecordManager{log, db, myClient, clientmap, nil} 27 + return &RecordManager{log, db, myClient, service, nil} 32 28 } 33 29 34 30 func (rm *RecordManager) SetBroadcaster(b LexBroadcaster) { 35 31 rm.broadcaster = b 36 32 } 37 - 38 - func (rm *RecordManager) getClient(id int, ctx context.Context) (*oauth.OauthXRPCClient, error) { 39 - cli, refreshed, err := rm.clientmap.Map(id, ctx) 40 - if cli == nil { 41 - rm.log.Deprintln("resetting client") 42 - cli, err = rm.resetClient(id, ctx) 43 - if err != nil { 44 - return nil, err 45 - } 46 - return cli, nil 47 - } 48 - 49 - if err != nil { 50 - return nil, errors.New("error getting client: " + err.Error()) 51 - } 52 - if refreshed { 53 - rm.log.Deprintln("refreshed") 54 - rm.db.UpdateSession(id, cli.GetSession(), ctx) 55 - } 56 - 57 - return cli, nil 58 - } 59 - 60 - func (rm *RecordManager) resetClient(id int, ctx context.Context) (*oauth.OauthXRPCClient, error) { 61 - session, err := rm.db.GetOauthSession(id, ctx) 62 - if err != nil { 63 - return nil, errors.New(fmt.Sprintf("errpr setting up session %d: %s", id, err.Error())) 64 - } 65 - return rm.setupClient(session), nil 66 - } 67 - 68 - func (rm *RecordManager) setupClient(session *types.Session) *oauth.OauthXRPCClient { 69 - client := oauth.NewOauthXRPCClient(rm.db, rm.log, session) 70 - rm.clientmap.Append(session.ID, client, session.Expiration) 71 - rm.log.Deprintf("appended cli %d", session.ID) 72 - if client == nil { 73 - rm.log.Println("client nil!") 74 - } 75 - return client 76 - } 77 - 78 - // create - oauth 79 - // store - db 80 - // broadcast - channels model
+7 -3
server/internal/recordmanager/session.go
··· 3 3 import ( 4 4 "context" 5 5 "errors" 6 + "github.com/bluesky-social/indigo/atproto/syntax" 6 7 ) 7 8 8 - func (rm *RecordManager) DeleteSession(id int, ctx context.Context) error { 9 - err := rm.db.DeleteOauthSession(id, ctx) 9 + func (rm *RecordManager) DeleteSession(did string, sessionID string, ctx context.Context) error { 10 + sdid, err := syntax.ParseDID(did) 11 + if err != nil { 12 + return errors.New("beep boop : " + err.Error()) 13 + } 14 + err = rm.db.DeleteSession(ctx, sdid, sessionID) 10 15 if err != nil { 11 16 return errors.New("failed to delete session: " + err.Error()) 12 17 } 13 - rm.clientmap.Delete(id) 14 18 return nil 15 19 }