An atproto PDS written in Go

Compare changes

Choose any two refs to compare.

Changed files
+1277 -684
cmd
cocoon
internal
db
metrics
models
oauth
server
templates
sqlite_blockstore
+1 -1
README.md
··· 256 256 257 257 ### Other 258 258 259 - - [ ] `com.atproto.label.queryLabels` 259 + - [x] `com.atproto.label.queryLabels` 260 260 - [x] `com.atproto.moderation.createReport` (Note: this should be handled by proxying, not actually implemented in the PDS) 261 261 - [x] `app.bsky.actor.getPreferences` 262 262 - [x] `app.bsky.actor.putPreferences`
+13
cmd/cocoon/main.go
··· 9 9 "os" 10 10 "time" 11 11 12 + "github.com/bluesky-social/go-util/pkg/telemetry" 12 13 "github.com/bluesky-social/indigo/atproto/atcrypto" 13 14 "github.com/bluesky-social/indigo/atproto/syntax" 14 15 "github.com/haileyok/cocoon/internal/helpers" ··· 79 80 Name: "admin-password", 80 81 EnvVars: []string{"COCOON_ADMIN_PASSWORD"}, 81 82 }, 83 + &cli.BoolFlag{ 84 + Name: "require-invite", 85 + EnvVars: []string{"COCOON_REQUIRE_INVITE"}, 86 + Value: true, 87 + }, 82 88 &cli.StringFlag{ 83 89 Name: "smtp-user", 84 90 EnvVars: []string{"COCOON_SMTP_USER"}, ··· 149 155 Name: "fallback-proxy", 150 156 EnvVars: []string{"COCOON_FALLBACK_PROXY"}, 151 157 }, 158 + telemetry.CLIFlagDebug, 159 + telemetry.CLIFlagMetricsListenAddress, 152 160 }, 153 161 Commands: []*cli.Command{ 154 162 runServe, ··· 172 180 Flags: []cli.Flag{}, 173 181 Action: func(cmd *cli.Context) error { 174 182 183 + logger := telemetry.StartLogger(cmd) 184 + telemetry.StartMetrics(cmd) 185 + 175 186 s, err := server.New(&server.Args{ 187 + Logger: logger, 176 188 Addr: cmd.String("addr"), 177 189 DbName: cmd.String("db-name"), 178 190 DbType: cmd.String("db-type"), ··· 185 197 Version: Version, 186 198 Relays: cmd.StringSlice("relays"), 187 199 AdminPassword: cmd.String("admin-password"), 200 + RequireInvite: cmd.Bool("require-invite"), 188 201 SmtpUser: cmd.String("smtp-user"), 189 202 SmtpPass: cmd.String("smtp-pass"), 190 203 SmtpHost: cmd.String("smtp-host"),
+19 -17
go.mod
··· 1 1 module github.com/haileyok/cocoon 2 2 3 - go 1.24.1 3 + go 1.24.5 4 4 5 5 require ( 6 6 github.com/Azure/go-autorest/autorest/to v0.4.1 7 7 github.com/aws/aws-sdk-go v1.55.7 8 + github.com/bluesky-social/go-util v0.0.0-20251012040650-2ebbf57f5934 8 9 github.com/bluesky-social/indigo v0.0.0-20251009212240-20524de167fe 9 10 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 10 11 github.com/domodwyer/mailyak/v3 v3.6.2 11 12 github.com/go-pkgz/expirable-cache/v3 v3.0.0 12 13 github.com/go-playground/validator v9.31.0+incompatible 13 14 github.com/golang-jwt/jwt/v4 v4.5.2 14 - github.com/google/uuid v1.4.0 15 + github.com/google/uuid v1.6.0 15 16 github.com/gorilla/sessions v1.4.0 16 17 github.com/gorilla/websocket v1.5.1 17 18 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b ··· 24 25 github.com/joho/godotenv v1.5.1 25 26 github.com/labstack/echo-contrib v0.17.4 26 27 github.com/labstack/echo/v4 v4.13.3 27 - github.com/lestrrat-go/jwx/v2 v2.0.12 28 + github.com/lestrrat-go/jwx/v2 v2.0.21 28 29 github.com/multiformats/go-multihash v0.2.3 30 + github.com/prometheus/client_golang v1.23.2 29 31 github.com/samber/slog-echo v1.16.1 30 32 github.com/urfave/cli/v2 v2.27.6 31 33 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e 32 34 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b 33 - golang.org/x/crypto v0.38.0 35 + golang.org/x/crypto v0.41.0 36 + gorm.io/driver/postgres v1.5.7 34 37 gorm.io/driver/sqlite v1.5.7 35 38 gorm.io/gorm v1.25.12 36 39 ) ··· 56 59 github.com/gorilla/securecookie v1.1.2 // indirect 57 60 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 58 61 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 59 - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect 62 + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 60 63 github.com/hashicorp/golang-lru v1.0.2 // indirect 61 64 github.com/ipfs/bbloom v0.0.4 // indirect 62 65 github.com/ipfs/go-blockservice v0.5.2 // indirect ··· 76 79 github.com/ipld/go-ipld-prime v0.21.0 // indirect 77 80 github.com/jackc/pgpassfile v1.0.0 // indirect 78 81 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 79 - github.com/jackc/pgx/v5 v5.5.0 // indirect 82 + github.com/jackc/pgx/v5 v5.5.4 // indirect 80 83 github.com/jackc/puddle/v2 v2.2.1 // indirect 81 84 github.com/jbenet/goprocess v0.1.4 // indirect 82 85 github.com/jinzhu/inflection v1.0.0 // indirect ··· 85 88 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 86 89 github.com/labstack/gommon v0.4.2 // indirect 87 90 github.com/leodido/go-urn v1.4.0 // indirect 88 - github.com/lestrrat-go/blackmagic v1.0.1 // indirect 91 + github.com/lestrrat-go/blackmagic v1.0.2 // indirect 89 92 github.com/lestrrat-go/httpcc v1.0.1 // indirect 90 - github.com/lestrrat-go/httprc v1.0.4 // indirect 93 + github.com/lestrrat-go/httprc v1.0.5 // indirect 91 94 github.com/lestrrat-go/iter v1.0.2 // indirect 92 95 github.com/lestrrat-go/option v1.0.1 // indirect 93 96 github.com/mattn/go-colorable v0.1.14 // indirect ··· 102 105 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 103 106 github.com/opentracing/opentracing-go v1.2.0 // indirect 104 107 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 105 - github.com/prometheus/client_golang v1.22.0 // indirect 106 108 github.com/prometheus/client_model v0.6.2 // indirect 107 - github.com/prometheus/common v0.63.0 // indirect 109 + github.com/prometheus/common v0.66.1 // indirect 108 110 github.com/prometheus/procfs v0.16.1 // indirect 109 111 github.com/russross/blackfriday/v2 v2.1.0 // indirect 110 112 github.com/samber/lo v1.49.1 // indirect ··· 114 116 github.com/valyala/fasttemplate v1.2.2 // indirect 115 117 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 116 118 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 117 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect 119 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect 118 120 go.opentelemetry.io/otel v1.29.0 // indirect 119 121 go.opentelemetry.io/otel/metric v1.29.0 // indirect 120 122 go.opentelemetry.io/otel/trace v1.29.0 // indirect 121 123 go.uber.org/atomic v1.11.0 // indirect 122 124 go.uber.org/multierr v1.11.0 // indirect 123 125 go.uber.org/zap v1.26.0 // indirect 124 - golang.org/x/net v0.40.0 // indirect 125 - golang.org/x/sync v0.14.0 // indirect 126 - golang.org/x/sys v0.33.0 // indirect 127 - golang.org/x/text v0.25.0 // indirect 126 + go.yaml.in/yaml/v2 v2.4.2 // indirect 127 + golang.org/x/net v0.43.0 // indirect 128 + golang.org/x/sync v0.16.0 // indirect 129 + golang.org/x/sys v0.35.0 // indirect 130 + golang.org/x/text v0.28.0 // indirect 128 131 golang.org/x/time v0.11.0 // indirect 129 132 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 130 - google.golang.org/protobuf v1.36.6 // indirect 133 + google.golang.org/protobuf v1.36.9 // indirect 131 134 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect 132 135 gopkg.in/inf.v0 v0.9.1 // indirect 133 - gorm.io/driver/postgres v1.5.7 // indirect 134 136 lukechampine.com/blake3 v1.2.1 // indirect 135 137 )
+50 -74
go.sum
··· 16 16 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 17 17 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= 18 18 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= 19 + github.com/bluesky-social/go-util v0.0.0-20251012040650-2ebbf57f5934 h1:btHMur2kTRgWEnCHn6LaI3BE9YRgsqTpwpJ1UdB7VEk= 20 + github.com/bluesky-social/go-util v0.0.0-20251012040650-2ebbf57f5934/go.mod h1:LWamyZfbQGW7PaVc5jumFfjgrshJ5mXgDUnR6fK7+BI= 19 21 github.com/bluesky-social/indigo v0.0.0-20251009212240-20524de167fe h1:VBhaqE5ewQgXbY5SfSWFZC/AwHFo7cHxZKFYi2ce9Yo= 20 22 github.com/bluesky-social/indigo v0.0.0-20251009212240-20524de167fe/go.mod h1:RuQVrCGm42QNsgumKaR6se+XkFKfCPNwdCiTvqKRUck= 21 23 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= ··· 34 36 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 35 37 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 36 38 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 - github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 38 39 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 39 40 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 40 41 github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= 41 42 github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= 43 + github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 44 + github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 42 45 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 43 46 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 44 47 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= ··· 77 80 github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 78 81 github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 79 82 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 80 - github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 81 - github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 83 + github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 84 + github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 82 85 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 83 86 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 84 87 github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= ··· 95 98 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= 96 99 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 97 100 github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 98 - github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= 99 - github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 100 - github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= 101 - github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 101 + github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= 102 + github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 103 + github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= 104 + github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 102 105 github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 103 106 github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 104 107 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= ··· 172 175 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 173 176 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= 174 177 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 175 - github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw= 176 - github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= 178 + github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= 179 + github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= 177 180 github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= 178 181 github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 179 182 github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= ··· 195 198 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 196 199 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 197 200 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 201 + github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 202 + github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 198 203 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 199 204 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 200 205 github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= ··· 206 211 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 207 212 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 208 213 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 214 + github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 215 + github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 209 216 github.com/labstack/echo-contrib v0.17.4 h1:g5mfsrJfJTKv+F5uNKCyrjLK7js+ZW6HTjg4FnDxxgk= 210 217 github.com/labstack/echo-contrib v0.17.4/go.mod h1:9O7ZPAHUeMGTOAfg80YqQduHzt0CzLak36PZRldYrZ0= 211 218 github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= ··· 214 221 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 215 222 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 216 223 github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 217 - github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= 218 - github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 224 + github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= 225 + github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 219 226 github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= 220 227 github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= 221 - github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= 222 - github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= 228 + github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= 229 + github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= 223 230 github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= 224 231 github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= 225 - github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA= 226 - github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ= 227 - github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 232 + github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55FHrR0= 233 + github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM= 228 234 github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= 229 235 github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 230 236 github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= ··· 289 295 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 290 296 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 291 297 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 292 - github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 293 - github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 298 + github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= 299 + github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= 294 300 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 295 301 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 296 - github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= 297 - github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= 302 + github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= 303 + github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= 298 304 github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 299 305 github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 300 306 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= ··· 317 323 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 318 324 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 319 325 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 320 - github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 321 - github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 322 - github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 323 326 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 324 327 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 325 328 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 326 329 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 327 330 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 328 - github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 329 - github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 330 - github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 331 - github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 331 + github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 332 + github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 332 333 github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 333 334 github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= 334 335 github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= ··· 349 350 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 350 351 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 351 352 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 352 - github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 353 353 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 354 354 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 355 355 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 356 356 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 357 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= 358 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= 357 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= 358 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= 359 359 go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= 360 360 go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= 361 361 go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= ··· 367 367 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 368 368 go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 369 369 go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 370 - go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 371 - go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= 370 + go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 371 + go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 372 372 go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 373 373 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 374 374 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= ··· 378 378 go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 379 379 go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 380 380 go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 381 + go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= 382 + go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= 381 383 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 382 384 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 383 385 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 384 386 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 385 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 386 - golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 387 - golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 388 - golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 387 + golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= 388 + golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 389 389 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= 390 390 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= 391 391 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= ··· 393 393 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 394 394 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 395 395 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 396 - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 397 - golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 398 - golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= 399 - golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 396 + golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= 397 + golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= 400 398 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 401 399 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 402 400 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 403 401 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 404 402 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 405 - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 406 403 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 407 - golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 408 - golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 409 - golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 410 - golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 411 - golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 404 + golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= 405 + golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 412 406 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 413 407 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 414 408 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 415 409 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 416 - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 417 - golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 418 - golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 419 - golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 410 + golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 411 + golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 420 412 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 421 413 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 422 414 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 423 415 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 424 416 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 425 417 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 426 - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 427 418 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 428 - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 429 - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 430 - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 431 419 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 432 420 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 433 - golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 434 - golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 435 - golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 436 - golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 421 + golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 422 + golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 437 423 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 438 - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 439 - golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 440 - golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 441 - golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 442 424 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 443 425 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 444 - golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 445 - golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 446 - golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 447 - golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 448 - golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 449 - golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 426 + golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= 427 + golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= 450 428 golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 451 429 golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 452 430 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= ··· 459 437 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 460 438 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 461 439 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 462 - golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 463 - golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 464 - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= 465 - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 440 + golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= 441 + golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= 466 442 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 467 443 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 468 444 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 469 445 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 470 446 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 471 447 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 472 - google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 473 - google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 448 + google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= 449 + google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 474 450 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 475 451 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 476 452 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+17 -34
internal/db/db.go
··· 1 1 package db 2 2 3 3 import ( 4 - "sync" 4 + "context" 5 5 6 6 "gorm.io/gorm" 7 7 "gorm.io/gorm/clause" ··· 9 9 10 10 type DB struct { 11 11 cli *gorm.DB 12 - mu sync.Mutex 13 12 } 14 13 15 14 func NewDB(cli *gorm.DB) *DB { 16 15 return &DB{ 17 16 cli: cli, 18 - mu: sync.Mutex{}, 19 17 } 20 18 } 21 19 22 - func (db *DB) Create(value any, clauses []clause.Expression) *gorm.DB { 23 - db.mu.Lock() 24 - defer db.mu.Unlock() 25 - return db.cli.Clauses(clauses...).Create(value) 20 + func (db *DB) Create(ctx context.Context, value any, clauses []clause.Expression) *gorm.DB { 21 + return db.cli.WithContext(ctx).Clauses(clauses...).Create(value) 26 22 } 27 23 28 - func (db *DB) Save(value any, clauses []clause.Expression) *gorm.DB { 29 - db.mu.Lock() 30 - defer db.mu.Unlock() 31 - return db.cli.Clauses(clauses...).Save(value) 24 + func (db *DB) Save(ctx context.Context, value any, clauses []clause.Expression) *gorm.DB { 25 + return db.cli.WithContext(ctx).Clauses(clauses...).Save(value) 32 26 } 33 27 34 - func (db *DB) Exec(sql string, clauses []clause.Expression, values ...any) *gorm.DB { 35 - db.mu.Lock() 36 - defer db.mu.Unlock() 37 - return db.cli.Clauses(clauses...).Exec(sql, values...) 28 + func (db *DB) Exec(ctx context.Context, sql string, clauses []clause.Expression, values ...any) *gorm.DB { 29 + return db.cli.WithContext(ctx).Clauses(clauses...).Exec(sql, values...) 38 30 } 39 31 40 - func (db *DB) Raw(sql string, clauses []clause.Expression, values ...any) *gorm.DB { 41 - return db.cli.Clauses(clauses...).Raw(sql, values...) 32 + func (db *DB) Raw(ctx context.Context, sql string, clauses []clause.Expression, values ...any) *gorm.DB { 33 + return db.cli.WithContext(ctx).Clauses(clauses...).Raw(sql, values...) 42 34 } 43 35 44 36 func (db *DB) AutoMigrate(models ...any) error { 45 37 return db.cli.AutoMigrate(models...) 46 38 } 47 39 48 - func (db *DB) Delete(value any, clauses []clause.Expression) *gorm.DB { 49 - db.mu.Lock() 50 - defer db.mu.Unlock() 51 - return db.cli.Clauses(clauses...).Delete(value) 40 + func (db *DB) Delete(ctx context.Context, value any, clauses []clause.Expression) *gorm.DB { 41 + return db.cli.WithContext(ctx).Clauses(clauses...).Delete(value) 52 42 } 53 43 54 - func (db *DB) First(dest any, conds ...any) *gorm.DB { 55 - return db.cli.First(dest, conds...) 44 + func (db *DB) First(ctx context.Context, dest any, conds ...any) *gorm.DB { 45 + return db.cli.WithContext(ctx).First(dest, conds...) 56 46 } 57 47 58 - // TODO: this isn't actually good. we can commit even if the db is locked here. this is probably okay for the time being, but need to figure 59 - // out a better solution. right now we only do this whenever we're importing a repo though so i'm mostly not worried, but it's still bad. 60 - // e.g. when we do apply writes we should also be using a transcation but we don't right now 61 - func (db *DB) BeginDangerously() *gorm.DB { 62 - return db.cli.Begin() 48 + func (db *DB) Begin(ctx context.Context) *gorm.DB { 49 + return db.cli.WithContext(ctx).Begin() 63 50 } 64 51 65 - func (db *DB) Lock() { 66 - db.mu.Lock() 67 - } 68 - 69 - func (db *DB) Unlock() { 70 - db.mu.Unlock() 52 + func (db *DB) Client() *gorm.DB { 53 + return db.cli 71 54 }
+30
metrics/metrics.go
··· 1 + package metrics 2 + 3 + import ( 4 + "github.com/prometheus/client_golang/prometheus" 5 + "github.com/prometheus/client_golang/prometheus/promauto" 6 + ) 7 + 8 + const ( 9 + NAMESPACE = "cocoon" 10 + ) 11 + 12 + var ( 13 + RelaysConnected = promauto.NewGaugeVec(prometheus.GaugeOpts{ 14 + Namespace: NAMESPACE, 15 + Name: "relays_connected", 16 + Help: "number of connected relays, by host", 17 + }, []string{"host"}) 18 + 19 + RelaySends = promauto.NewCounterVec(prometheus.CounterOpts{ 20 + Namespace: NAMESPACE, 21 + Name: "relay_sends", 22 + Help: "number of events sent to a relay, by host", 23 + }, []string{"host", "kind"}) 24 + 25 + RepoOperations = promauto.NewCounterVec(prometheus.CounterOpts{ 26 + Namespace: NAMESPACE, 27 + Name: "repo_operations", 28 + Help: "number of operations made against repos", 29 + }, []string{"kind"}) 30 + )
+12 -2
models/models.go
··· 8 8 "github.com/bluesky-social/indigo/atproto/atcrypto" 9 9 ) 10 10 11 + type TwoFactorType string 12 + 13 + var ( 14 + TwoFactorTypeNone = TwoFactorType("none") 15 + TwoFactorTypeEmail = TwoFactorType("email") 16 + ) 17 + 11 18 type Repo struct { 12 19 Did string `gorm:"primaryKey"` 13 20 CreatedAt time.Time ··· 29 36 Root []byte 30 37 Preferences []byte 31 38 Deactivated bool 39 + TwoFactorCode *string 40 + TwoFactorCodeExpiresAt *time.Time 41 + TwoFactorType TwoFactorType `gorm:"default:none"` 32 42 } 33 43 34 44 func (r *Repo) SignFor(ctx context.Context, did string, msg []byte) ([]byte, error) { ··· 121 131 } 122 132 123 133 type ReservedKey struct { 124 - KeyDid string `gorm:"primaryKey"` 125 - Did *string `gorm:"index"` 134 + KeyDid string `gorm:"primaryKey"` 135 + Did *string `gorm:"index"` 126 136 PrivateKey []byte 127 137 CreatedAt time.Time `gorm:"index"` 128 138 }
-1
oauth/client/manager.go
··· 72 72 } 73 73 74 74 jwks = k 75 - } else if metadata.JWKS != nil { 76 75 } else if metadata.JWKSURI != nil { 77 76 maybeJwks, err := cm.getClientJwks(ctx, clientId, *metadata.JWKSURI) 78 77 if err != nil {
+1 -1
oauth/dpop/jti_cache.go
··· 14 14 } 15 15 16 16 func newJTICache(size int) *jtiCache { 17 - cache := cache.NewCache[string, bool]().WithTTL(24 * time.Hour).WithLRU().WithTTL(constants.JTITtl) 17 + cache := cache.NewCache[string, bool]().WithTTL(24 * time.Hour).WithLRU().WithTTL(constants.JTITtl).WithMaxKeys(size) 18 18 return &jtiCache{ 19 19 cache: cache, 20 20 mu: sync.Mutex{},
+4 -4
oauth/dpop/manager.go
··· 75 75 } 76 76 77 77 proof := extractProof(headers) 78 - 79 78 if proof == "" { 80 79 return nil, nil 81 80 } ··· 197 196 198 197 nonce, _ := claims["nonce"].(string) 199 198 if nonce == "" { 200 - // WARN: this _must_ be `use_dpop_nonce` for clients know they should make another request 199 + // reference impl checks if self.nonce is not null before returning an error, but we always have a 200 + // nonce so we do not bother checking 201 201 return nil, ErrUseDpopNonce 202 202 } 203 203 204 204 if nonce != "" && !dm.nonce.Check(nonce) { 205 - // WARN: this _must_ be `use_dpop_nonce` so that clients will fetch a new nonce 205 + // dpop nonce mismatch 206 206 return nil, ErrUseDpopNonce 207 207 } 208 208 ··· 237 237 } 238 238 239 239 func extractProof(headers http.Header) string { 240 - dpopHeaders := headers["Dpop"] 240 + dpopHeaders := headers.Values("dpop") 241 241 switch len(dpopHeaders) { 242 242 case 0: 243 243 return ""
+3 -3
oauth/provider/client_auth.go
··· 19 19 } 20 20 21 21 type AuthenticateClientRequestBase struct { 22 - ClientID string `form:"client_id" json:"client_id" validate:"required"` 23 - ClientAssertionType *string `form:"client_assertion_type" json:"client_assertion_type,omitempty"` 24 - ClientAssertion *string `form:"client_assertion" json:"client_assertion,omitempty"` 22 + ClientID string `form:"client_id" json:"client_id" query:"client_id" validate:"required"` 23 + ClientAssertionType *string `form:"client_assertion_type" json:"client_assertion_type,omitempty" query:"client_assertion_type"` 24 + ClientAssertion *string `form:"client_assertion" json:"client_assertion,omitempty" query:"client_assertion"` 25 25 } 26 26 27 27 func (p *Provider) AuthenticateClient(ctx context.Context, req AuthenticateClientRequestBase, proof *dpop.Proof, opts *AuthenticateClientOptions) (*client.Client, *ClientAuth, error) {
+9 -8
oauth/provider/models.go
··· 32 32 33 33 type ParRequest struct { 34 34 AuthenticateClientRequestBase 35 - ResponseType string `form:"response_type" json:"response_type" validate:"required"` 36 - CodeChallenge *string `form:"code_challenge" json:"code_challenge" validate:"required"` 37 - CodeChallengeMethod string `form:"code_challenge_method" json:"code_challenge_method" validate:"required"` 38 - State string `form:"state" json:"state" validate:"required"` 39 - RedirectURI string `form:"redirect_uri" json:"redirect_uri" validate:"required"` 40 - Scope string `form:"scope" json:"scope" validate:"required"` 41 - LoginHint *string `form:"login_hint" json:"login_hint,omitempty"` 42 - DpopJkt *string `form:"dpop_jkt" json:"dpop_jkt,omitempty"` 35 + ResponseType string `form:"response_type" json:"response_type" query:"response_type" validate:"required"` 36 + CodeChallenge *string `form:"code_challenge" json:"code_challenge" query:"code_challenge" validate:"required"` 37 + CodeChallengeMethod string `form:"code_challenge_method" json:"code_challenge_method" query:"code_challenge_method" validate:"required"` 38 + State string `form:"state" json:"state" query:"state" validate:"required"` 39 + RedirectURI string `form:"redirect_uri" json:"redirect_uri" query:"redirect_uri" validate:"required"` 40 + Scope string `form:"scope" json:"scope" query:"scope" validate:"required"` 41 + LoginHint *string `form:"login_hint" query:"login_hint" json:"login_hint,omitempty"` 42 + DpopJkt *string `form:"dpop_jkt" query:"dpop_jkt" json:"dpop_jkt,omitempty"` 43 + ResponseMode *string `form:"response_mode" json:"response_mode,omitempty" query:"response_mode"` 43 44 } 44 45 45 46 func (opr *ParRequest) Scan(value any) error {
+10 -8
server/common.go
··· 1 1 package server 2 2 3 3 import ( 4 + "context" 5 + 4 6 "github.com/haileyok/cocoon/models" 5 7 ) 6 8 7 - func (s *Server) getActorByHandle(handle string) (*models.Actor, error) { 9 + func (s *Server) getActorByHandle(ctx context.Context, handle string) (*models.Actor, error) { 8 10 var actor models.Actor 9 - if err := s.db.First(&actor, models.Actor{Handle: handle}).Error; err != nil { 11 + if err := s.db.First(ctx, &actor, models.Actor{Handle: handle}).Error; err != nil { 10 12 return nil, err 11 13 } 12 14 return &actor, nil 13 15 } 14 16 15 - func (s *Server) getRepoByEmail(email string) (*models.Repo, error) { 17 + func (s *Server) getRepoByEmail(ctx context.Context, email string) (*models.Repo, error) { 16 18 var repo models.Repo 17 - if err := s.db.First(&repo, models.Repo{Email: email}).Error; err != nil { 19 + if err := s.db.First(ctx, &repo, models.Repo{Email: email}).Error; err != nil { 18 20 return nil, err 19 21 } 20 22 return &repo, nil 21 23 } 22 24 23 - func (s *Server) getRepoActorByEmail(email string) (*models.RepoActor, error) { 25 + func (s *Server) getRepoActorByEmail(ctx context.Context, email string) (*models.RepoActor, error) { 24 26 var repo models.RepoActor 25 - if err := s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.email= ?", nil, email).Scan(&repo).Error; err != nil { 27 + if err := s.db.Raw(ctx, "SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.email= ?", nil, email).Scan(&repo).Error; err != nil { 26 28 return nil, err 27 29 } 28 30 return &repo, nil 29 31 } 30 32 31 - func (s *Server) getRepoActorByDid(did string) (*models.RepoActor, error) { 33 + func (s *Server) getRepoActorByDid(ctx context.Context, did string) (*models.RepoActor, error) { 32 34 var repo models.RepoActor 33 - if err := s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", nil, did).Scan(&repo).Error; err != nil { 35 + if err := s.db.Raw(ctx, "SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", nil, did).Scan(&repo).Error; err != nil { 34 36 return nil, err 35 37 } 36 38 return &repo, nil
+4 -2
server/handle_account.go
··· 12 12 13 13 func (s *Server) handleAccount(e echo.Context) error { 14 14 ctx := e.Request().Context() 15 + logger := s.logger.With("name", "handleAuth") 16 + 15 17 repo, sess, err := s.getSessionRepoOrErr(e) 16 18 if err != nil { 17 19 return e.Redirect(303, "/account/signin") ··· 20 22 oldestPossibleSession := time.Now().Add(constants.ConfidentialClientSessionLifetime) 21 23 22 24 var tokens []provider.OauthToken 23 - if err := s.db.Raw("SELECT * FROM oauth_tokens WHERE sub = ? AND created_at < ? ORDER BY created_at ASC", nil, repo.Repo.Did, oldestPossibleSession).Scan(&tokens).Error; err != nil { 24 - s.logger.Error("couldnt fetch oauth sessions for account", "did", repo.Repo.Did, "error", err) 25 + if err := s.db.Raw(ctx, "SELECT * FROM oauth_tokens WHERE sub = ? AND created_at < ? ORDER BY created_at ASC", nil, repo.Repo.Did, oldestPossibleSession).Scan(&tokens).Error; err != nil { 26 + logger.Error("couldnt fetch oauth sessions for account", "did", repo.Repo.Did, "error", err) 25 27 sess.AddFlash("Unable to fetch sessions. See server logs for more details.", "error") 26 28 sess.Save(e.Request(), e.Response()) 27 29 return e.Render(200, "account.html", map[string]any{
+8 -5
server/handle_account_revoke.go
··· 5 5 "github.com/labstack/echo/v4" 6 6 ) 7 7 8 - type AccountRevokeRequest struct { 8 + type AccountRevokeInput struct { 9 9 Token string `form:"token"` 10 10 } 11 11 12 12 func (s *Server) handleAccountRevoke(e echo.Context) error { 13 - var req AccountRevokeRequest 13 + ctx := e.Request().Context() 14 + logger := s.logger.With("name", "handleAcocuntRevoke") 15 + 16 + var req AccountRevokeInput 14 17 if err := e.Bind(&req); err != nil { 15 - s.logger.Error("could not bind account revoke request", "error", err) 18 + logger.Error("could not bind account revoke request", "error", err) 16 19 return helpers.ServerError(e, nil) 17 20 } 18 21 ··· 21 24 return e.Redirect(303, "/account/signin") 22 25 } 23 26 24 - if err := s.db.Exec("DELETE FROM oauth_tokens WHERE sub = ? AND token = ?", nil, repo.Repo.Did, req.Token).Error; err != nil { 25 - s.logger.Error("couldnt delete oauth session for account", "did", repo.Repo.Did, "token", req.Token, "error", err) 27 + if err := s.db.Exec(ctx, "DELETE FROM oauth_tokens WHERE sub = ? AND token = ?", nil, repo.Repo.Did, req.Token).Error; err != nil { 28 + logger.Error("couldnt delete oauth session for account", "did", repo.Repo.Did, "token", req.Token, "error", err) 26 29 sess.AddFlash("Unable to revoke session. See server logs for more details.", "error") 27 30 sess.Save(e.Request(), e.Response()) 28 31 return e.Redirect(303, "/account")
+68 -16
server/handle_account_signin.go
··· 2 2 3 3 import ( 4 4 "errors" 5 + "fmt" 5 6 "strings" 7 + "time" 6 8 7 9 "github.com/bluesky-social/indigo/atproto/syntax" 8 10 "github.com/gorilla/sessions" ··· 14 16 "gorm.io/gorm" 15 17 ) 16 18 17 - type OauthSigninRequest struct { 18 - Username string `form:"username"` 19 - Password string `form:"password"` 20 - QueryParams string `form:"query_params"` 19 + type OauthSigninInput struct { 20 + Username string `form:"username"` 21 + Password string `form:"password"` 22 + AuthFactorToken string `form:"token"` 23 + QueryParams string `form:"query_params"` 21 24 } 22 25 23 26 func (s *Server) getSessionRepoOrErr(e echo.Context) (*models.RepoActor, *sessions.Session, error) { 27 + ctx := e.Request().Context() 28 + 24 29 sess, err := session.Get("session", e) 25 30 if err != nil { 26 31 return nil, nil, err ··· 31 36 return nil, sess, errors.New("did was not set in session") 32 37 } 33 38 34 - repo, err := s.getRepoActorByDid(did) 39 + repo, err := s.getRepoActorByDid(ctx, did) 35 40 if err != nil { 36 41 return nil, sess, err 37 42 } ··· 42 47 func getFlashesFromSession(e echo.Context, sess *sessions.Session) map[string]any { 43 48 defer sess.Save(e.Request(), e.Response()) 44 49 return map[string]any{ 45 - "errors": sess.Flashes("error"), 46 - "successes": sess.Flashes("success"), 50 + "errors": sess.Flashes("error"), 51 + "successes": sess.Flashes("success"), 52 + "tokenrequired": sess.Flashes("tokenrequired"), 47 53 } 48 54 } 49 55 ··· 60 66 } 61 67 62 68 func (s *Server) handleAccountSigninPost(e echo.Context) error { 63 - var req OauthSigninRequest 69 + ctx := e.Request().Context() 70 + logger := s.logger.With("name", "handleAccountSigninPost") 71 + 72 + var req OauthSigninInput 64 73 if err := e.Bind(&req); err != nil { 65 - s.logger.Error("error binding sign in req", "error", err) 74 + logger.Error("error binding sign in req", "error", err) 66 75 return helpers.ServerError(e, nil) 67 76 } 68 77 ··· 76 85 idtype = "handle" 77 86 } else { 78 87 idtype = "email" 88 + } 89 + 90 + queryParams := "" 91 + if req.QueryParams != "" { 92 + queryParams = fmt.Sprintf("?%s", req.QueryParams) 79 93 } 80 94 81 95 // TODO: we should make this a helper since we do it for the base create_session as well ··· 83 97 var err error 84 98 switch idtype { 85 99 case "did": 86 - err = s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", nil, req.Username).Scan(&repo).Error 100 + err = s.db.Raw(ctx, "SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", nil, req.Username).Scan(&repo).Error 87 101 case "handle": 88 - err = s.db.Raw("SELECT r.*, a.* FROM actors a LEFT JOIN repos r ON a.did = r.did WHERE a.handle = ?", nil, req.Username).Scan(&repo).Error 102 + err = s.db.Raw(ctx, "SELECT r.*, a.* FROM actors a LEFT JOIN repos r ON a.did = r.did WHERE a.handle = ?", nil, req.Username).Scan(&repo).Error 89 103 case "email": 90 - err = s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.email = ?", nil, req.Username).Scan(&repo).Error 104 + err = s.db.Raw(ctx, "SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.email = ?", nil, req.Username).Scan(&repo).Error 91 105 } 92 106 if err != nil { 93 107 if err == gorm.ErrRecordNotFound { ··· 96 110 sess.AddFlash("Something went wrong!", "error") 97 111 } 98 112 sess.Save(e.Request(), e.Response()) 99 - return e.Redirect(303, "/account/signin") 113 + return e.Redirect(303, "/account/signin"+queryParams) 100 114 } 101 115 102 116 if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil { ··· 106 120 sess.AddFlash("Something went wrong!", "error") 107 121 } 108 122 sess.Save(e.Request(), e.Response()) 109 - return e.Redirect(303, "/account/signin") 123 + return e.Redirect(303, "/account/signin"+queryParams) 124 + } 125 + 126 + // if repo requires 2FA token and one hasn't been provided, return error prompting for one 127 + if repo.TwoFactorType != models.TwoFactorTypeNone && req.AuthFactorToken == "" { 128 + err = s.createAndSendTwoFactorCode(ctx, repo) 129 + if err != nil { 130 + sess.AddFlash("Something went wrong!", "error") 131 + sess.Save(e.Request(), e.Response()) 132 + return e.Redirect(303, "/account/signin"+queryParams) 133 + } 134 + 135 + sess.AddFlash("requires 2FA token", "tokenrequired") 136 + sess.Save(e.Request(), e.Response()) 137 + return e.Redirect(303, "/account/signin"+queryParams) 138 + } 139 + 140 + // if 2FAis required, now check that the one provided is valid 141 + if repo.TwoFactorType != models.TwoFactorTypeNone { 142 + if repo.TwoFactorCode == nil || repo.TwoFactorCodeExpiresAt == nil { 143 + err = s.createAndSendTwoFactorCode(ctx, repo) 144 + if err != nil { 145 + sess.AddFlash("Something went wrong!", "error") 146 + sess.Save(e.Request(), e.Response()) 147 + return e.Redirect(303, "/account/signin"+queryParams) 148 + } 149 + 150 + sess.AddFlash("requires 2FA token", "tokenrequired") 151 + sess.Save(e.Request(), e.Response()) 152 + return e.Redirect(303, "/account/signin"+queryParams) 153 + } 154 + 155 + if *repo.TwoFactorCode != req.AuthFactorToken { 156 + return helpers.InvalidTokenError(e) 157 + } 158 + 159 + if time.Now().UTC().After(*repo.TwoFactorCodeExpiresAt) { 160 + return helpers.ExpiredTokenError(e) 161 + } 110 162 } 111 163 112 164 sess.Options = &sessions.Options{ ··· 122 174 return err 123 175 } 124 176 125 - if req.QueryParams != "" { 126 - return e.Redirect(303, "/oauth/authorize?"+req.QueryParams) 177 + if queryParams != "" { 178 + return e.Redirect(303, "/oauth/authorize"+queryParams) 127 179 } else { 128 180 return e.Redirect(303, "/account") 129 181 }
+3 -1
server/handle_actor_put_preferences.go
··· 10 10 // This is kinda lame. Not great to implement app.bsky in the pds, but alas 11 11 12 12 func (s *Server) handleActorPutPreferences(e echo.Context) error { 13 + ctx := e.Request().Context() 14 + 13 15 repo := e.Get("repo").(*models.RepoActor) 14 16 15 17 var prefs map[string]any ··· 22 24 return err 23 25 } 24 26 25 - if err := s.db.Exec("UPDATE repos SET preferences = ? WHERE did = ?", nil, b, repo.Repo.Did).Error; err != nil { 27 + if err := s.db.Exec(ctx, "UPDATE repos SET preferences = ? WHERE did = ?", nil, b, repo.Repo.Did).Error; err != nil { 26 28 return err 27 29 } 28 30
+6 -3
server/handle_identity_request_plc_operation.go
··· 10 10 ) 11 11 12 12 func (s *Server) handleIdentityRequestPlcOperationSignature(e echo.Context) error { 13 + ctx := e.Request().Context() 14 + logger := s.logger.With("name", "handleIdentityRequestPlcOperationSignature") 15 + 13 16 urepo := e.Get("repo").(*models.RepoActor) 14 17 15 18 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 16 19 eat := time.Now().Add(10 * time.Minute).UTC() 17 20 18 - if err := s.db.Exec("UPDATE repos SET plc_operation_code = ?, plc_operation_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 19 - s.logger.Error("error updating user", "error", err) 21 + if err := s.db.Exec(ctx, "UPDATE repos SET plc_operation_code = ?, plc_operation_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 22 + logger.Error("error updating user", "error", err) 20 23 return helpers.ServerError(e, nil) 21 24 } 22 25 23 26 if err := s.sendPlcTokenReset(urepo.Email, urepo.Handle, code); err != nil { 24 - s.logger.Error("error sending mail", "error", err) 27 + logger.Error("error sending mail", "error", err) 25 28 return helpers.ServerError(e, nil) 26 29 } 27 30
+8 -6
server/handle_identity_sign_plc_operation.go
··· 27 27 } 28 28 29 29 func (s *Server) handleSignPlcOperation(e echo.Context) error { 30 + logger := s.logger.With("name", "handleSignPlcOperation") 31 + 30 32 repo := e.Get("repo").(*models.RepoActor) 31 33 32 34 var req ComAtprotoSignPlcOperationRequest 33 35 if err := e.Bind(&req); err != nil { 34 - s.logger.Error("error binding", "error", err) 36 + logger.Error("error binding", "error", err) 35 37 return helpers.ServerError(e, nil) 36 38 } 37 39 ··· 54 56 ctx := context.WithValue(e.Request().Context(), "skip-cache", true) 55 57 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did) 56 58 if err != nil { 57 - s.logger.Error("error fetching doc", "error", err) 59 + logger.Error("error fetching doc", "error", err) 58 60 return helpers.ServerError(e, nil) 59 61 } 60 62 ··· 83 85 84 86 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey) 85 87 if err != nil { 86 - s.logger.Error("error parsing signing key", "error", err) 88 + logger.Error("error parsing signing key", "error", err) 87 89 return helpers.ServerError(e, nil) 88 90 } 89 91 90 92 if err := s.plcClient.SignOp(k, &op); err != nil { 91 - s.logger.Error("error signing plc operation", "error", err) 93 + logger.Error("error signing plc operation", "error", err) 92 94 return helpers.ServerError(e, nil) 93 95 } 94 96 95 - if err := s.db.Exec("UPDATE repos SET plc_operation_code = NULL, plc_operation_code_expires_at = NULL WHERE did = ?", nil, repo.Repo.Did).Error; err != nil { 96 - s.logger.Error("error updating repo", "error", err) 97 + if err := s.db.Exec(ctx, "UPDATE repos SET plc_operation_code = NULL, plc_operation_code_expires_at = NULL WHERE did = ?", nil, repo.Repo.Did).Error; err != nil { 98 + logger.Error("error updating repo", "error", err) 97 99 return helpers.ServerError(e, nil) 98 100 } 99 101
+6 -4
server/handle_identity_submit_plc_operation.go
··· 21 21 } 22 22 23 23 func (s *Server) handleSubmitPlcOperation(e echo.Context) error { 24 + logger := s.logger.With("name", "handleIdentitySubmitPlcOperation") 25 + 24 26 repo := e.Get("repo").(*models.RepoActor) 25 27 26 28 var req ComAtprotoSubmitPlcOperationRequest 27 29 if err := e.Bind(&req); err != nil { 28 - s.logger.Error("error binding", "error", err) 30 + logger.Error("error binding", "error", err) 29 31 return helpers.ServerError(e, nil) 30 32 } 31 33 ··· 40 42 41 43 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey) 42 44 if err != nil { 43 - s.logger.Error("error parsing key", "error", err) 45 + logger.Error("error parsing key", "error", err) 44 46 return helpers.ServerError(e, nil) 45 47 } 46 48 required, err := s.plcClient.CreateDidCredentials(k, "", repo.Actor.Handle) 47 49 if err != nil { 48 - s.logger.Error("error crating did credentials", "error", err) 50 + logger.Error("error crating did credentials", "error", err) 49 51 return helpers.ServerError(e, nil) 50 52 } 51 53 ··· 72 74 } 73 75 74 76 if err := s.passport.BustDoc(context.TODO(), repo.Repo.Did); err != nil { 75 - s.logger.Warn("error busting did doc", "error", err) 77 + logger.Warn("error busting did doc", "error", err) 76 78 } 77 79 78 80 s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
+8 -6
server/handle_identity_update_handle.go
··· 22 22 } 23 23 24 24 func (s *Server) handleIdentityUpdateHandle(e echo.Context) error { 25 + logger := s.logger.With("name", "handleIdentityUpdateHandle") 26 + 25 27 repo := e.Get("repo").(*models.RepoActor) 26 28 27 29 var req ComAtprotoIdentityUpdateHandleRequest 28 30 if err := e.Bind(&req); err != nil { 29 - s.logger.Error("error binding", "error", err) 31 + logger.Error("error binding", "error", err) 30 32 return helpers.ServerError(e, nil) 31 33 } 32 34 ··· 41 43 if strings.HasPrefix(repo.Repo.Did, "did:plc:") { 42 44 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did) 43 45 if err != nil { 44 - s.logger.Error("error fetching doc", "error", err) 46 + logger.Error("error fetching doc", "error", err) 45 47 return helpers.ServerError(e, nil) 46 48 } 47 49 ··· 68 70 69 71 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey) 70 72 if err != nil { 71 - s.logger.Error("error parsing signing key", "error", err) 73 + logger.Error("error parsing signing key", "error", err) 72 74 return helpers.ServerError(e, nil) 73 75 } 74 76 ··· 82 84 } 83 85 84 86 if err := s.passport.BustDoc(context.TODO(), repo.Repo.Did); err != nil { 85 - s.logger.Warn("error busting did doc", "error", err) 87 + logger.Warn("error busting did doc", "error", err) 86 88 } 87 89 88 90 s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ ··· 94 96 }, 95 97 }) 96 98 97 - if err := s.db.Exec("UPDATE actors SET handle = ? WHERE did = ?", nil, req.Handle, repo.Repo.Did).Error; err != nil { 98 - s.logger.Error("error updating handle in db", "error", err) 99 + if err := s.db.Exec(ctx, "UPDATE actors SET handle = ? WHERE did = ?", nil, req.Handle, repo.Repo.Did).Error; err != nil { 100 + logger.Error("error updating handle in db", "error", err) 99 101 return helpers.ServerError(e, nil) 100 102 } 101 103
+14 -11
server/handle_import_repo.go
··· 18 18 ) 19 19 20 20 func (s *Server) handleRepoImportRepo(e echo.Context) error { 21 + ctx := e.Request().Context() 22 + logger := s.logger.With("name", "handleImportRepo") 23 + 21 24 urepo := e.Get("repo").(*models.RepoActor) 22 25 23 26 b, err := io.ReadAll(e.Request().Body) 24 27 if err != nil { 25 - s.logger.Error("could not read bytes in import request", "error", err) 28 + logger.Error("could not read bytes in import request", "error", err) 26 29 return helpers.ServerError(e, nil) 27 30 } 28 31 ··· 30 33 31 34 cs, err := car.NewCarReader(bytes.NewReader(b)) 32 35 if err != nil { 33 - s.logger.Error("could not read car in import request", "error", err) 36 + logger.Error("could not read car in import request", "error", err) 34 37 return helpers.ServerError(e, nil) 35 38 } 36 39 37 40 orderedBlocks := []blocks.Block{} 38 41 currBlock, err := cs.Next() 39 42 if err != nil { 40 - s.logger.Error("could not get first block from car", "error", err) 43 + logger.Error("could not get first block from car", "error", err) 41 44 return helpers.ServerError(e, nil) 42 45 } 43 46 currBlockCt := 1 44 47 45 48 for currBlock != nil { 46 - s.logger.Info("someone is importing their repo", "block", currBlockCt) 49 + logger.Info("someone is importing their repo", "block", currBlockCt) 47 50 orderedBlocks = append(orderedBlocks, currBlock) 48 51 next, _ := cs.Next() 49 52 currBlock = next ··· 53 56 slices.Reverse(orderedBlocks) 54 57 55 58 if err := bs.PutMany(context.TODO(), orderedBlocks); err != nil { 56 - s.logger.Error("could not insert blocks", "error", err) 59 + logger.Error("could not insert blocks", "error", err) 57 60 return helpers.ServerError(e, nil) 58 61 } 59 62 60 63 r, err := repo.OpenRepo(context.TODO(), bs, cs.Header.Roots[0]) 61 64 if err != nil { 62 - s.logger.Error("could not open repo", "error", err) 65 + logger.Error("could not open repo", "error", err) 63 66 return helpers.ServerError(e, nil) 64 67 } 65 68 66 - tx := s.db.BeginDangerously() 69 + tx := s.db.Begin(ctx) 67 70 68 71 clock := syntax.NewTIDClock(0) 69 72 ··· 74 77 cidStr := cid.String() 75 78 b, err := bs.Get(context.TODO(), cid) 76 79 if err != nil { 77 - s.logger.Error("record bytes don't exist in blockstore", "error", err) 80 + logger.Error("record bytes don't exist in blockstore", "error", err) 78 81 return helpers.ServerError(e, nil) 79 82 } 80 83 ··· 94 97 return nil 95 98 }); err != nil { 96 99 tx.Rollback() 97 - s.logger.Error("record bytes don't exist in blockstore", "error", err) 100 + logger.Error("record bytes don't exist in blockstore", "error", err) 98 101 return helpers.ServerError(e, nil) 99 102 } 100 103 ··· 102 105 103 106 root, rev, err := r.Commit(context.TODO(), urepo.SignFor) 104 107 if err != nil { 105 - s.logger.Error("error committing", "error", err) 108 + logger.Error("error committing", "error", err) 106 109 return helpers.ServerError(e, nil) 107 110 } 108 111 109 112 if err := s.UpdateRepo(context.TODO(), urepo.Repo.Did, root, rev); err != nil { 110 - s.logger.Error("error updating repo after commit", "error", err) 113 + logger.Error("error updating repo after commit", "error", err) 111 114 return helpers.ServerError(e, nil) 112 115 } 113 116
+34
server/handle_label_query_labels.go
··· 1 + package server 2 + 3 + import ( 4 + "github.com/labstack/echo/v4" 5 + ) 6 + 7 + type Label struct { 8 + Ver *int `json:"ver,omitempty"` 9 + Src string `json:"src"` 10 + Uri string `json:"uri"` 11 + Cid *string `json:"cid,omitempty"` 12 + Val string `json:"val"` 13 + Neg *bool `json:"neg,omitempty"` 14 + Cts string `json:"cts"` 15 + Exp *string `json:"exp,omitempty"` 16 + Sig []byte `json:"sig,omitempty"` 17 + } 18 + 19 + type ComAtprotoLabelQueryLabelsResponse struct { 20 + Cursor *string `json:"cursor,omitempty"` 21 + Labels []Label `json:"labels"` 22 + } 23 + 24 + func (s *Server) handleLabelQueryLabels(e echo.Context) error { 25 + svc := e.Request().Header.Get("atproto-proxy") 26 + if svc != "" || s.config.FallbackProxy != "" { 27 + return s.handleProxy(e) 28 + } 29 + 30 + return e.JSON(200, ComAtprotoLabelQueryLabelsResponse{ 31 + Cursor: nil, 32 + Labels: []Label{}, 33 + }) 34 + }
+105 -24
server/handle_oauth_authorize.go
··· 1 1 package server 2 2 3 3 import ( 4 + "fmt" 4 5 "net/url" 5 6 "strings" 6 7 "time" ··· 8 9 "github.com/Azure/go-autorest/autorest/to" 9 10 "github.com/haileyok/cocoon/internal/helpers" 10 11 "github.com/haileyok/cocoon/oauth" 12 + "github.com/haileyok/cocoon/oauth/constants" 11 13 "github.com/haileyok/cocoon/oauth/provider" 12 14 "github.com/labstack/echo/v4" 13 15 ) 14 16 17 + type HandleOauthAuthorizeGetInput struct { 18 + RequestUri string `query:"request_uri"` 19 + } 20 + 15 21 func (s *Server) handleOauthAuthorizeGet(e echo.Context) error { 16 - reqUri := e.QueryParam("request_uri") 17 - if reqUri == "" { 18 - // render page for logged out dev 19 - if s.config.Version == "dev" { 20 - return e.Render(200, "authorize.html", map[string]any{ 21 - "Scopes": []string{"atproto", "transition:generic"}, 22 - "AppName": "DEV MODE AUTHORIZATION PAGE", 23 - "Handle": "paula.cocoon.social", 24 - "RequestUri": "", 25 - }) 22 + ctx := e.Request().Context() 23 + 24 + logger := s.logger.With("name", "handleOauthAuthorizeGet") 25 + 26 + var input HandleOauthAuthorizeGetInput 27 + if err := e.Bind(&input); err != nil { 28 + logger.Error("error binding request", "err", err) 29 + return fmt.Errorf("error binding request") 30 + } 31 + 32 + var reqId string 33 + if input.RequestUri != "" { 34 + id, err := oauth.DecodeRequestUri(input.RequestUri) 35 + if err != nil { 36 + logger.Error("no request uri found in input", "url", e.Request().URL.String()) 37 + return helpers.InputError(e, to.StringPtr("no request uri")) 38 + } 39 + reqId = id 40 + } else { 41 + var parRequest provider.ParRequest 42 + if err := e.Bind(&parRequest); err != nil { 43 + s.logger.Error("error binding for standard auth request", "error", err) 44 + return helpers.InputError(e, to.StringPtr("InvalidRequest")) 45 + } 46 + 47 + if err := e.Validate(parRequest); err != nil { 48 + // render page for logged out dev 49 + if s.config.Version == "dev" && parRequest.ClientID == "" { 50 + return e.Render(200, "authorize.html", map[string]any{ 51 + "Scopes": []string{"atproto", "transition:generic"}, 52 + "AppName": "DEV MODE AUTHORIZATION PAGE", 53 + "Handle": "paula.cocoon.social", 54 + "RequestUri": "", 55 + }) 56 + } 57 + return helpers.InputError(e, to.StringPtr("no request uri and invalid parameters")) 58 + } 59 + 60 + client, clientAuth, err := s.oauthProvider.AuthenticateClient(ctx, parRequest.AuthenticateClientRequestBase, nil, &provider.AuthenticateClientOptions{ 61 + AllowMissingDpopProof: true, 62 + }) 63 + if err != nil { 64 + s.logger.Error("error authenticating client in standard request", "client_id", parRequest.ClientID, "error", err) 65 + return helpers.ServerError(e, to.StringPtr(err.Error())) 66 + } 67 + 68 + if parRequest.DpopJkt == nil { 69 + if client.Metadata.DpopBoundAccessTokens { 70 + } 71 + } else { 72 + if !client.Metadata.DpopBoundAccessTokens { 73 + msg := "dpop bound access tokens are not enabled for this client" 74 + return helpers.InputError(e, &msg) 75 + } 76 + } 77 + 78 + eat := time.Now().Add(constants.ParExpiresIn) 79 + id := oauth.GenerateRequestId() 80 + 81 + authRequest := &provider.OauthAuthorizationRequest{ 82 + RequestId: id, 83 + ClientId: client.Metadata.ClientID, 84 + ClientAuth: *clientAuth, 85 + Parameters: parRequest, 86 + ExpiresAt: eat, 26 87 } 27 - return helpers.InputError(e, to.StringPtr("no request uri")) 88 + 89 + if err := s.db.Create(ctx, authRequest, nil).Error; err != nil { 90 + s.logger.Error("error creating auth request in db", "error", err) 91 + return helpers.ServerError(e, nil) 92 + } 93 + 94 + input.RequestUri = oauth.EncodeRequestUri(id) 95 + reqId = id 96 + 28 97 } 29 98 30 99 repo, _, err := s.getSessionRepoOrErr(e) ··· 32 101 return e.Redirect(303, "/account/signin?"+e.QueryParams().Encode()) 33 102 } 34 103 35 - reqId, err := oauth.DecodeRequestUri(reqUri) 36 - if err != nil { 37 - return helpers.InputError(e, to.StringPtr(err.Error())) 38 - } 39 - 40 104 var req provider.OauthAuthorizationRequest 41 - if err := s.db.Raw("SELECT * FROM oauth_authorization_requests WHERE request_id = ?", nil, reqId).Scan(&req).Error; err != nil { 105 + if err := s.db.Raw(ctx, "SELECT * FROM oauth_authorization_requests WHERE request_id = ?", nil, reqId).Scan(&req).Error; err != nil { 42 106 return helpers.ServerError(e, to.StringPtr(err.Error())) 43 107 } 44 108 ··· 58 122 data := map[string]any{ 59 123 "Scopes": scopes, 60 124 "AppName": appName, 61 - "RequestUri": reqUri, 125 + "RequestUri": input.RequestUri, 62 126 "QueryParams": e.QueryParams().Encode(), 63 127 "Handle": repo.Actor.Handle, 64 128 } ··· 72 136 } 73 137 74 138 func (s *Server) handleOauthAuthorizePost(e echo.Context) error { 139 + ctx := e.Request().Context() 140 + logger := s.logger.With("name", "handleOauthAuthorizePost") 141 + 75 142 repo, _, err := s.getSessionRepoOrErr(e) 76 143 if err != nil { 77 144 return e.Redirect(303, "/account/signin") ··· 79 146 80 147 var req OauthAuthorizePostRequest 81 148 if err := e.Bind(&req); err != nil { 82 - s.logger.Error("error binding authorize post request", "error", err) 149 + logger.Error("error binding authorize post request", "error", err) 83 150 return helpers.InputError(e, nil) 84 151 } 85 152 ··· 89 156 } 90 157 91 158 var authReq provider.OauthAuthorizationRequest 92 - if err := s.db.Raw("SELECT * FROM oauth_authorization_requests WHERE request_id = ?", nil, reqId).Scan(&authReq).Error; err != nil { 159 + if err := s.db.Raw(ctx, "SELECT * FROM oauth_authorization_requests WHERE request_id = ?", nil, reqId).Scan(&authReq).Error; err != nil { 93 160 return helpers.ServerError(e, to.StringPtr(err.Error())) 94 161 } 95 162 ··· 113 180 114 181 code := oauth.GenerateCode() 115 182 116 - if err := s.db.Exec("UPDATE oauth_authorization_requests SET sub = ?, code = ?, accepted = ?, ip = ? WHERE request_id = ?", nil, repo.Repo.Did, code, true, e.RealIP(), reqId).Error; err != nil { 117 - s.logger.Error("error updating authorization request", "error", err) 183 + if err := s.db.Exec(ctx, "UPDATE oauth_authorization_requests SET sub = ?, code = ?, accepted = ?, ip = ? WHERE request_id = ?", nil, repo.Repo.Did, code, true, e.RealIP(), reqId).Error; err != nil { 184 + logger.Error("error updating authorization request", "error", err) 118 185 return helpers.ServerError(e, nil) 119 186 } 120 187 ··· 124 191 q.Set("code", code) 125 192 126 193 hashOrQuestion := "?" 127 - if authReq.ClientAuth.Method != "private_key_jwt" { 128 - hashOrQuestion = "#" 194 + if authReq.Parameters.ResponseMode != nil { 195 + switch *authReq.Parameters.ResponseMode { 196 + case "fragment": 197 + hashOrQuestion = "#" 198 + case "query": 199 + // do nothing 200 + break 201 + default: 202 + if authReq.Parameters.ResponseType != "code" { 203 + hashOrQuestion = "#" 204 + } 205 + } 206 + } else { 207 + if authReq.Parameters.ResponseType != "code" { 208 + hashOrQuestion = "#" 209 + } 129 210 } 130 211 131 212 return e.Redirect(303, authReq.Parameters.RedirectURI+hashOrQuestion+q.Encode())
+12 -8
server/handle_oauth_par.go
··· 19 19 } 20 20 21 21 func (s *Server) handleOauthPar(e echo.Context) error { 22 + ctx := e.Request().Context() 23 + logger := s.logger.With("name", "handleOauthPar") 24 + 22 25 var parRequest provider.ParRequest 23 26 if err := e.Bind(&parRequest); err != nil { 24 - s.logger.Error("error binding for par request", "error", err) 27 + logger.Error("error binding for par request", "error", err) 25 28 return helpers.ServerError(e, nil) 26 29 } 27 30 28 31 if err := e.Validate(parRequest); err != nil { 29 - s.logger.Error("missing parameters for par request", "error", err) 32 + logger.Error("missing parameters for par request", "error", err) 30 33 return helpers.InputError(e, nil) 31 34 } 32 35 ··· 39 42 e.Response().Header().Set("DPoP-Nonce", nonce) 40 43 e.Response().Header().Add("access-control-expose-headers", "DPoP-Nonce") 41 44 } 45 + logger.Error("nonce error: use_dpop_nonce", "headers", e.Request().Header) 42 46 return e.JSON(400, map[string]string{ 43 47 "error": "use_dpop_nonce", 44 48 }) 45 49 } 46 - s.logger.Error("error getting dpop proof", "error", err) 50 + logger.Error("error getting dpop proof", "error", err) 47 51 return helpers.InputError(e, nil) 48 52 } 49 53 ··· 53 57 AllowMissingDpopProof: true, 54 58 }) 55 59 if err != nil { 56 - s.logger.Error("error authenticating client", "client_id", parRequest.ClientID, "error", err) 60 + logger.Error("error authenticating client", "client_id", parRequest.ClientID, "error", err) 57 61 return helpers.InputError(e, to.StringPtr(err.Error())) 58 62 } 59 63 ··· 64 68 } else { 65 69 if !client.Metadata.DpopBoundAccessTokens { 66 70 msg := "dpop bound access tokens are not enabled for this client" 67 - s.logger.Error(msg) 71 + logger.Error(msg) 68 72 return helpers.InputError(e, &msg) 69 73 } 70 74 71 75 if dpopProof.JKT != *parRequest.DpopJkt { 72 76 msg := "supplied dpop jkt does not match header dpop jkt" 73 - s.logger.Error(msg) 77 + logger.Error(msg) 74 78 return helpers.InputError(e, &msg) 75 79 } 76 80 } ··· 86 90 ExpiresAt: eat, 87 91 } 88 92 89 - if err := s.db.Create(authRequest, nil).Error; err != nil { 90 - s.logger.Error("error creating auth request in db", "error", err) 93 + if err := s.db.Create(ctx, authRequest, nil).Error; err != nil { 94 + logger.Error("error creating auth request in db", "error", err) 91 95 return helpers.ServerError(e, nil) 92 96 } 93 97
+16 -13
server/handle_oauth_token.go
··· 38 38 } 39 39 40 40 func (s *Server) handleOauthToken(e echo.Context) error { 41 + ctx := e.Request().Context() 42 + logger := s.logger.With("name", "handleOauthToken") 43 + 41 44 var req OauthTokenRequest 42 45 if err := e.Bind(&req); err != nil { 43 - s.logger.Error("error binding token request", "error", err) 46 + logger.Error("error binding token request", "error", err) 44 47 return helpers.ServerError(e, nil) 45 48 } 46 49 ··· 56 59 "error": "use_dpop_nonce", 57 60 }) 58 61 } 59 - s.logger.Error("error getting dpop proof", "error", err) 62 + logger.Error("error getting dpop proof", "error", err) 60 63 return helpers.InputError(e, nil) 61 64 } 62 65 ··· 64 67 AllowMissingDpopProof: true, 65 68 }) 66 69 if err != nil { 67 - s.logger.Error("error authenticating client", "client_id", req.ClientID, "error", err) 70 + logger.Error("error authenticating client", "client_id", req.ClientID, "error", err) 68 71 return helpers.InputError(e, to.StringPtr(err.Error())) 69 72 } 70 73 ··· 84 87 85 88 var authReq provider.OauthAuthorizationRequest 86 89 // get the lil guy and delete him 87 - if err := s.db.Raw("DELETE FROM oauth_authorization_requests WHERE code = ? RETURNING *", nil, *req.Code).Scan(&authReq).Error; err != nil { 88 - s.logger.Error("error finding authorization request", "error", err) 90 + if err := s.db.Raw(ctx, "DELETE FROM oauth_authorization_requests WHERE code = ? RETURNING *", nil, *req.Code).Scan(&authReq).Error; err != nil { 91 + logger.Error("error finding authorization request", "error", err) 89 92 return helpers.ServerError(e, nil) 90 93 } 91 94 ··· 110 113 case "S256": 111 114 inputChal, err := base64.RawURLEncoding.DecodeString(*authReq.Parameters.CodeChallenge) 112 115 if err != nil { 113 - s.logger.Error("error decoding code challenge", "error", err) 116 + logger.Error("error decoding code challenge", "error", err) 114 117 return helpers.ServerError(e, nil) 115 118 } 116 119 ··· 128 131 return helpers.InputError(e, to.StringPtr("code_challenge parameter wasn't provided")) 129 132 } 130 133 131 - repo, err := s.getRepoActorByDid(*authReq.Sub) 134 + repo, err := s.getRepoActorByDid(ctx, *authReq.Sub) 132 135 if err != nil { 133 136 helpers.InputError(e, to.StringPtr("unable to find actor")) 134 137 } ··· 159 162 return err 160 163 } 161 164 162 - if err := s.db.Create(&provider.OauthToken{ 165 + if err := s.db.Create(ctx, &provider.OauthToken{ 163 166 ClientId: authReq.ClientId, 164 167 ClientAuth: *clientAuth, 165 168 Parameters: authReq.Parameters, ··· 171 174 RefreshToken: refreshToken, 172 175 Ip: authReq.Ip, 173 176 }, nil).Error; err != nil { 174 - s.logger.Error("error creating token in db", "error", err) 177 + logger.Error("error creating token in db", "error", err) 175 178 return helpers.ServerError(e, nil) 176 179 } 177 180 ··· 199 202 } 200 203 201 204 var oauthToken provider.OauthToken 202 - if err := s.db.Raw("SELECT * FROM oauth_tokens WHERE refresh_token = ?", nil, req.RefreshToken).Scan(&oauthToken).Error; err != nil { 203 - s.logger.Error("error finding oauth token by refresh token", "error", err, "refresh_token", req.RefreshToken) 205 + if err := s.db.Raw(ctx, "SELECT * FROM oauth_tokens WHERE refresh_token = ?", nil, req.RefreshToken).Scan(&oauthToken).Error; err != nil { 206 + logger.Error("error finding oauth token by refresh token", "error", err, "refresh_token", req.RefreshToken) 204 207 return helpers.ServerError(e, nil) 205 208 } 206 209 ··· 257 260 return err 258 261 } 259 262 260 - if err := s.db.Exec("UPDATE oauth_tokens SET token = ?, refresh_token = ?, expires_at = ?, updated_at = ? WHERE refresh_token = ?", nil, accessString, nextRefreshToken, eat, now, *req.RefreshToken).Error; err != nil { 261 - s.logger.Error("error updating token", "error", err) 263 + if err := s.db.Exec(ctx, "UPDATE oauth_tokens SET token = ?, refresh_token = ?, expires_at = ?, updated_at = ? WHERE refresh_token = ?", nil, accessString, nextRefreshToken, eat, now, *req.RefreshToken).Error; err != nil { 264 + logger.Error("error updating token", "error", err) 262 265 return helpers.ServerError(e, nil) 263 266 } 264 267
+6 -6
server/handle_proxy.go
··· 47 47 } 48 48 49 49 func (s *Server) handleProxy(e echo.Context) error { 50 - lgr := s.logger.With("handler", "handleProxy") 50 + logger := s.logger.With("handler", "handleProxy") 51 51 52 52 repo, isAuthed := e.Get("repo").(*models.RepoActor) 53 53 ··· 58 58 59 59 endpoint, svcDid, err := s.getAtprotoProxyEndpointFromRequest(e) 60 60 if err != nil { 61 - lgr.Error("could not get atproto proxy", "error", err) 61 + logger.Error("could not get atproto proxy", "error", err) 62 62 return helpers.ServerError(e, nil) 63 63 } 64 64 ··· 90 90 } 91 91 hj, err := json.Marshal(header) 92 92 if err != nil { 93 - lgr.Error("error marshaling header", "error", err) 93 + logger.Error("error marshaling header", "error", err) 94 94 return helpers.ServerError(e, nil) 95 95 } 96 96 ··· 118 118 } 119 119 pj, err := json.Marshal(payload) 120 120 if err != nil { 121 - lgr.Error("error marashaling payload", "error", err) 121 + logger.Error("error marashaling payload", "error", err) 122 122 return helpers.ServerError(e, nil) 123 123 } 124 124 ··· 129 129 130 130 sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey) 131 131 if err != nil { 132 - lgr.Error("can't load private key", "error", err) 132 + logger.Error("can't load private key", "error", err) 133 133 return err 134 134 } 135 135 136 136 R, S, _, err := sk.SignRaw(rand.Reader, hash[:]) 137 137 if err != nil { 138 - lgr.Error("error signing", "error", err) 138 + logger.Error("error signing", "error", err) 139 139 } 140 140 141 141 rBytes := R.Bytes()
+14 -11
server/handle_repo_apply_writes.go
··· 6 6 "github.com/labstack/echo/v4" 7 7 ) 8 8 9 - type ComAtprotoRepoApplyWritesRequest struct { 9 + type ComAtprotoRepoApplyWritesInput struct { 10 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 11 Validate *bool `json:"bool,omitempty"` 12 12 Writes []ComAtprotoRepoApplyWritesItem `json:"writes"` ··· 20 20 Value *MarshalableMap `json:"value,omitempty"` 21 21 } 22 22 23 - type ComAtprotoRepoApplyWritesResponse struct { 23 + type ComAtprotoRepoApplyWritesOutput struct { 24 24 Commit RepoCommit `json:"commit"` 25 25 Results []ApplyWriteResult `json:"results"` 26 26 } 27 27 28 28 func (s *Server) handleApplyWrites(e echo.Context) error { 29 - repo := e.Get("repo").(*models.RepoActor) 29 + ctx := e.Request().Context() 30 + logger := s.logger.With("name", "handleRepoApplyWrites") 30 31 31 - var req ComAtprotoRepoApplyWritesRequest 32 + var req ComAtprotoRepoApplyWritesInput 32 33 if err := e.Bind(&req); err != nil { 33 - s.logger.Error("error binding", "error", err) 34 + logger.Error("error binding", "error", err) 34 35 return helpers.ServerError(e, nil) 35 36 } 36 37 37 38 if err := e.Validate(req); err != nil { 38 - s.logger.Error("error validating", "error", err) 39 + logger.Error("error validating", "error", err) 39 40 return helpers.InputError(e, nil) 40 41 } 41 42 43 + repo := e.Get("repo").(*models.RepoActor) 44 + 42 45 if repo.Repo.Did != req.Repo { 43 - s.logger.Warn("mismatched repo/auth") 46 + logger.Warn("mismatched repo/auth") 44 47 return helpers.InputError(e, nil) 45 48 } 46 49 47 - ops := []Op{} 50 + ops := make([]Op, 0, len(req.Writes)) 48 51 for _, item := range req.Writes { 49 52 ops = append(ops, Op{ 50 53 Type: OpType(item.Type), ··· 54 57 }) 55 58 } 56 59 57 - results, err := s.repoman.applyWrites(repo.Repo, ops, req.SwapCommit) 60 + results, err := s.repoman.applyWrites(ctx, repo.Repo, ops, req.SwapCommit) 58 61 if err != nil { 59 - s.logger.Error("error applying writes", "error", err) 62 + logger.Error("error applying writes", "error", err) 60 63 return helpers.ServerError(e, nil) 61 64 } 62 65 ··· 66 69 results[i].Commit = nil 67 70 } 68 71 69 - return e.JSON(200, ComAtprotoRepoApplyWritesResponse{ 72 + return e.JSON(200, ComAtprotoRepoApplyWritesOutput{ 70 73 Commit: commit, 71 74 Results: results, 72 75 })
+10 -7
server/handle_repo_create_record.go
··· 6 6 "github.com/labstack/echo/v4" 7 7 ) 8 8 9 - type ComAtprotoRepoCreateRecordRequest struct { 9 + type ComAtprotoRepoCreateRecordInput struct { 10 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 11 Collection string `json:"collection" validate:"required,atproto-nsid"` 12 12 Rkey *string `json:"rkey,omitempty"` ··· 17 17 } 18 18 19 19 func (s *Server) handleCreateRecord(e echo.Context) error { 20 + ctx := e.Request().Context() 21 + logger := s.logger.With("name", "handleCreateRecord") 22 + 20 23 repo := e.Get("repo").(*models.RepoActor) 21 24 22 - var req ComAtprotoRepoCreateRecordRequest 25 + var req ComAtprotoRepoCreateRecordInput 23 26 if err := e.Bind(&req); err != nil { 24 - s.logger.Error("error binding", "error", err) 27 + logger.Error("error binding", "error", err) 25 28 return helpers.ServerError(e, nil) 26 29 } 27 30 28 31 if err := e.Validate(req); err != nil { 29 - s.logger.Error("error validating", "error", err) 32 + logger.Error("error validating", "error", err) 30 33 return helpers.InputError(e, nil) 31 34 } 32 35 33 36 if repo.Repo.Did != req.Repo { 34 - s.logger.Warn("mismatched repo/auth") 37 + logger.Warn("mismatched repo/auth") 35 38 return helpers.InputError(e, nil) 36 39 } 37 40 ··· 40 43 optype = OpTypeUpdate 41 44 } 42 45 43 - results, err := s.repoman.applyWrites(repo.Repo, []Op{ 46 + results, err := s.repoman.applyWrites(ctx, repo.Repo, []Op{ 44 47 { 45 48 Type: optype, 46 49 Collection: req.Collection, ··· 51 54 }, 52 55 }, req.SwapCommit) 53 56 if err != nil { 54 - s.logger.Error("error applying writes", "error", err) 57 + logger.Error("error applying writes", "error", err) 55 58 return helpers.ServerError(e, nil) 56 59 } 57 60
+10 -7
server/handle_repo_delete_record.go
··· 6 6 "github.com/labstack/echo/v4" 7 7 ) 8 8 9 - type ComAtprotoRepoDeleteRecordRequest struct { 9 + type ComAtprotoRepoDeleteRecordInput struct { 10 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 11 Collection string `json:"collection" validate:"required,atproto-nsid"` 12 12 Rkey string `json:"rkey" validate:"required,atproto-rkey"` ··· 15 15 } 16 16 17 17 func (s *Server) handleDeleteRecord(e echo.Context) error { 18 + ctx := e.Request().Context() 19 + logger := s.logger.With("name", "handleDeleteRecord") 20 + 18 21 repo := e.Get("repo").(*models.RepoActor) 19 22 20 - var req ComAtprotoRepoDeleteRecordRequest 23 + var req ComAtprotoRepoDeleteRecordInput 21 24 if err := e.Bind(&req); err != nil { 22 - s.logger.Error("error binding", "error", err) 25 + logger.Error("error binding", "error", err) 23 26 return helpers.ServerError(e, nil) 24 27 } 25 28 26 29 if err := e.Validate(req); err != nil { 27 - s.logger.Error("error validating", "error", err) 30 + logger.Error("error validating", "error", err) 28 31 return helpers.InputError(e, nil) 29 32 } 30 33 31 34 if repo.Repo.Did != req.Repo { 32 - s.logger.Warn("mismatched repo/auth") 35 + logger.Warn("mismatched repo/auth") 33 36 return helpers.InputError(e, nil) 34 37 } 35 38 36 - results, err := s.repoman.applyWrites(repo.Repo, []Op{ 39 + results, err := s.repoman.applyWrites(ctx, repo.Repo, []Op{ 37 40 { 38 41 Type: OpTypeDelete, 39 42 Collection: req.Collection, ··· 42 45 }, 43 46 }, req.SwapCommit) 44 47 if err != nil { 45 - s.logger.Error("error applying writes", "error", err) 48 + logger.Error("error applying writes", "error", err) 46 49 return helpers.ServerError(e, nil) 47 50 } 48 51
+8 -5
server/handle_repo_describe_repo.go
··· 20 20 } 21 21 22 22 func (s *Server) handleDescribeRepo(e echo.Context) error { 23 + ctx := e.Request().Context() 24 + logger := s.logger.With("name", "handleDescribeRepo") 25 + 23 26 did := e.QueryParam("repo") 24 - repo, err := s.getRepoActorByDid(did) 27 + repo, err := s.getRepoActorByDid(ctx, did) 25 28 if err != nil { 26 29 if err == gorm.ErrRecordNotFound { 27 30 return helpers.InputError(e, to.StringPtr("RepoNotFound")) 28 31 } 29 32 30 - s.logger.Error("error looking up repo", "error", err) 33 + logger.Error("error looking up repo", "error", err) 31 34 return helpers.ServerError(e, nil) 32 35 } 33 36 ··· 35 38 36 39 diddoc, err := s.passport.FetchDoc(e.Request().Context(), repo.Repo.Did) 37 40 if err != nil { 38 - s.logger.Error("error fetching diddoc", "error", err) 41 + logger.Error("error fetching diddoc", "error", err) 39 42 return helpers.ServerError(e, nil) 40 43 } 41 44 ··· 64 67 } 65 68 66 69 var records []models.Record 67 - if err := s.db.Raw("SELECT DISTINCT(nsid) FROM records WHERE did = ?", nil, repo.Repo.Did).Scan(&records).Error; err != nil { 68 - s.logger.Error("error getting collections", "error", err) 70 + if err := s.db.Raw(ctx, "SELECT DISTINCT(nsid) FROM records WHERE did = ?", nil, repo.Repo.Did).Scan(&records).Error; err != nil { 71 + logger.Error("error getting collections", "error", err) 69 72 return helpers.ServerError(e, nil) 70 73 } 71 74
+3 -1
server/handle_repo_get_record.go
··· 14 14 } 15 15 16 16 func (s *Server) handleRepoGetRecord(e echo.Context) error { 17 + ctx := e.Request().Context() 18 + 17 19 repo := e.QueryParam("repo") 18 20 collection := e.QueryParam("collection") 19 21 rkey := e.QueryParam("rkey") ··· 32 34 } 33 35 34 36 var record models.Record 35 - if err := s.db.Raw("SELECT * FROM records WHERE did = ? AND nsid = ? AND rkey = ?"+cidquery, nil, params...).Scan(&record).Error; err != nil { 37 + if err := s.db.Raw(ctx, "SELECT * FROM records WHERE did = ? AND nsid = ? AND rkey = ?"+cidquery, nil, params...).Scan(&record).Error; err != nil { 36 38 // TODO: handle error nicely 37 39 return err 38 40 }
+6 -3
server/handle_repo_list_missing_blobs.go
··· 22 22 } 23 23 24 24 func (s *Server) handleListMissingBlobs(e echo.Context) error { 25 + ctx := e.Request().Context() 26 + logger := s.logger.With("name", "handleListMissingBlos") 27 + 25 28 urepo := e.Get("repo").(*models.RepoActor) 26 29 27 30 limitStr := e.QueryParam("limit") ··· 35 38 } 36 39 37 40 var records []models.Record 38 - if err := s.db.Raw("SELECT * FROM records WHERE did = ?", nil, urepo.Repo.Did).Scan(&records).Error; err != nil { 39 - s.logger.Error("failed to get records for listMissingBlobs", "error", err) 41 + if err := s.db.Raw(ctx, "SELECT * FROM records WHERE did = ?", nil, urepo.Repo.Did).Scan(&records).Error; err != nil { 42 + logger.Error("failed to get records for listMissingBlobs", "error", err) 40 43 return helpers.ServerError(e, nil) 41 44 } 42 45 ··· 69 72 } 70 73 71 74 var count int64 72 - if err := s.db.Raw("SELECT COUNT(*) FROM blobs WHERE did = ? AND cid = ?", nil, urepo.Repo.Did, ref.cid.Bytes()).Scan(&count).Error; err != nil { 75 + if err := s.db.Raw(ctx, "SELECT COUNT(*) FROM blobs WHERE did = ? AND cid = ?", nil, urepo.Repo.Did, ref.cid.Bytes()).Scan(&count).Error; err != nil { 73 76 continue 74 77 } 75 78
+7 -4
server/handle_repo_list_records.go
··· 46 46 } 47 47 48 48 func (s *Server) handleListRecords(e echo.Context) error { 49 + ctx := e.Request().Context() 50 + logger := s.logger.With("name", "handleListRecords") 51 + 49 52 var req ComAtprotoRepoListRecordsRequest 50 53 if err := e.Bind(&req); err != nil { 51 - s.logger.Error("could not bind list records request", "error", err) 54 + logger.Error("could not bind list records request", "error", err) 52 55 return helpers.ServerError(e, nil) 53 56 } 54 57 ··· 78 81 79 82 did := req.Repo 80 83 if _, err := syntax.ParseDID(did); err != nil { 81 - actor, err := s.getActorByHandle(req.Repo) 84 + actor, err := s.getActorByHandle(ctx, req.Repo) 82 85 if err != nil { 83 86 return helpers.InputError(e, to.StringPtr("RepoNotFound")) 84 87 } ··· 93 96 params = append(params, limit) 94 97 95 98 var records []models.Record 96 - if err := s.db.Raw("SELECT * FROM records WHERE did = ? AND nsid = ? "+cursorquery+" ORDER BY created_at "+sort+" limit ?", nil, params...).Scan(&records).Error; err != nil { 97 - s.logger.Error("error getting records", "error", err) 99 + if err := s.db.Raw(ctx, "SELECT * FROM records WHERE did = ? AND nsid = ? "+cursorquery+" ORDER BY created_at "+sort+" limit ?", nil, params...).Scan(&records).Error; err != nil { 100 + logger.Error("error getting records", "error", err) 98 101 return helpers.ServerError(e, nil) 99 102 } 100 103
+3 -1
server/handle_repo_list_repos.go
··· 21 21 22 22 // TODO: paginate this bitch 23 23 func (s *Server) handleListRepos(e echo.Context) error { 24 + ctx := e.Request().Context() 25 + 24 26 var repos []models.Repo 25 - if err := s.db.Raw("SELECT * FROM repos ORDER BY created_at DESC LIMIT 500", nil).Scan(&repos).Error; err != nil { 27 + if err := s.db.Raw(ctx, "SELECT * FROM repos ORDER BY created_at DESC LIMIT 500", nil).Scan(&repos).Error; err != nil { 26 28 return err 27 29 } 28 30
+10 -7
server/handle_repo_put_record.go
··· 6 6 "github.com/labstack/echo/v4" 7 7 ) 8 8 9 - type ComAtprotoRepoPutRecordRequest struct { 9 + type ComAtprotoRepoPutRecordInput struct { 10 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 11 Collection string `json:"collection" validate:"required,atproto-nsid"` 12 12 Rkey string `json:"rkey" validate:"required,atproto-rkey"` ··· 17 17 } 18 18 19 19 func (s *Server) handlePutRecord(e echo.Context) error { 20 + ctx := e.Request().Context() 21 + logger := s.logger.With("name", "handlePutRecord") 22 + 20 23 repo := e.Get("repo").(*models.RepoActor) 21 24 22 - var req ComAtprotoRepoPutRecordRequest 25 + var req ComAtprotoRepoPutRecordInput 23 26 if err := e.Bind(&req); err != nil { 24 - s.logger.Error("error binding", "error", err) 27 + logger.Error("error binding", "error", err) 25 28 return helpers.ServerError(e, nil) 26 29 } 27 30 28 31 if err := e.Validate(req); err != nil { 29 - s.logger.Error("error validating", "error", err) 32 + logger.Error("error validating", "error", err) 30 33 return helpers.InputError(e, nil) 31 34 } 32 35 33 36 if repo.Repo.Did != req.Repo { 34 - s.logger.Warn("mismatched repo/auth") 37 + logger.Warn("mismatched repo/auth") 35 38 return helpers.InputError(e, nil) 36 39 } 37 40 ··· 40 43 optype = OpTypeUpdate 41 44 } 42 45 43 - results, err := s.repoman.applyWrites(repo.Repo, []Op{ 46 + results, err := s.repoman.applyWrites(ctx, repo.Repo, []Op{ 44 47 { 45 48 Type: optype, 46 49 Collection: req.Collection, ··· 51 54 }, 52 55 }, req.SwapCommit) 53 56 if err != nil { 54 - s.logger.Error("error applying writes", "error", err) 57 + logger.Error("error applying writes", "error", err) 55 58 return helpers.ServerError(e, nil) 56 59 } 57 60
+13 -10
server/handle_repo_upload_blob.go
··· 32 32 } 33 33 34 34 func (s *Server) handleRepoUploadBlob(e echo.Context) error { 35 + ctx := e.Request().Context() 36 + logger := s.logger.With("name", "handleRepoUploadBlob") 37 + 35 38 urepo := e.Get("repo").(*models.RepoActor) 36 39 37 40 mime := e.Request().Header.Get("content-type") ··· 51 54 Storage: storage, 52 55 } 53 56 54 - if err := s.db.Create(&blob, nil).Error; err != nil { 55 - s.logger.Error("error creating new blob in db", "error", err) 57 + if err := s.db.Create(ctx, &blob, nil).Error; err != nil { 58 + logger.Error("error creating new blob in db", "error", err) 56 59 return helpers.ServerError(e, nil) 57 60 } 58 61 ··· 69 72 break 70 73 } 71 74 } else if err != nil && err != io.ErrUnexpectedEOF { 72 - s.logger.Error("error reading blob", "error", err) 75 + logger.Error("error reading blob", "error", err) 73 76 return helpers.ServerError(e, nil) 74 77 } 75 78 ··· 84 87 Data: data, 85 88 } 86 89 87 - if err := s.db.Create(&blobPart, nil).Error; err != nil { 88 - s.logger.Error("error adding blob part to db", "error", err) 90 + if err := s.db.Create(ctx, &blobPart, nil).Error; err != nil { 91 + logger.Error("error adding blob part to db", "error", err) 89 92 return helpers.ServerError(e, nil) 90 93 } 91 94 } ··· 98 101 99 102 c, err := cid.NewPrefixV1(cid.Raw, multihash.SHA2_256).Sum(fulldata.Bytes()) 100 103 if err != nil { 101 - s.logger.Error("error creating cid prefix", "error", err) 104 + logger.Error("error creating cid prefix", "error", err) 102 105 return helpers.ServerError(e, nil) 103 106 } 104 107 ··· 115 118 116 119 sess, err := session.NewSession(config) 117 120 if err != nil { 118 - s.logger.Error("error creating aws session", "error", err) 121 + logger.Error("error creating aws session", "error", err) 119 122 return helpers.ServerError(e, nil) 120 123 } 121 124 ··· 126 129 Key: aws.String(fmt.Sprintf("blobs/%s/%s", urepo.Repo.Did, c.String())), 127 130 Body: bytes.NewReader(fulldata.Bytes()), 128 131 }); err != nil { 129 - s.logger.Error("error uploading blob to s3", "error", err) 132 + logger.Error("error uploading blob to s3", "error", err) 130 133 return helpers.ServerError(e, nil) 131 134 } 132 135 } 133 136 134 - if err := s.db.Exec("UPDATE blobs SET cid = ? WHERE id = ?", nil, c.Bytes(), blob.ID).Error; err != nil { 137 + if err := s.db.Exec(ctx, "UPDATE blobs SET cid = ? WHERE id = ?", nil, c.Bytes(), blob.ID).Error; err != nil { 135 138 // there should probably be somme handling here if this fails... 136 - s.logger.Error("error updating blob", "error", err) 139 + logger.Error("error updating blob", "error", err) 137 140 return helpers.ServerError(e, nil) 138 141 } 139 142
+6 -3
server/handle_server_activate_account.go
··· 18 18 } 19 19 20 20 func (s *Server) handleServerActivateAccount(e echo.Context) error { 21 + ctx := e.Request().Context() 22 + logger := s.logger.With("name", "handleServerActivateAccount") 23 + 21 24 var req ComAtprotoServerDeactivateAccountRequest 22 25 if err := e.Bind(&req); err != nil { 23 - s.logger.Error("error binding", "error", err) 26 + logger.Error("error binding", "error", err) 24 27 return helpers.ServerError(e, nil) 25 28 } 26 29 27 30 urepo := e.Get("repo").(*models.RepoActor) 28 31 29 - if err := s.db.Exec("UPDATE repos SET deactivated = ? WHERE did = ?", nil, false, urepo.Repo.Did).Error; err != nil { 30 - s.logger.Error("error updating account status to deactivated", "error", err) 32 + if err := s.db.Exec(ctx, "UPDATE repos SET deactivated = ? WHERE did = ?", nil, false, urepo.Repo.Did).Error; err != nil { 33 + logger.Error("error updating account status to deactivated", "error", err) 31 34 return helpers.ServerError(e, nil) 32 35 } 33 36
+10 -7
server/handle_server_check_account_status.go
··· 20 20 } 21 21 22 22 func (s *Server) handleServerCheckAccountStatus(e echo.Context) error { 23 + ctx := e.Request().Context() 24 + logger := s.logger.With("name", "handleServerCheckAccountStatus") 25 + 23 26 urepo := e.Get("repo").(*models.RepoActor) 24 27 25 28 resp := ComAtprotoServerCheckAccountStatusResponse{ ··· 31 34 32 35 rootcid, err := cid.Cast(urepo.Root) 33 36 if err != nil { 34 - s.logger.Error("error casting cid", "error", err) 37 + logger.Error("error casting cid", "error", err) 35 38 return helpers.ServerError(e, nil) 36 39 } 37 40 resp.RepoCommit = rootcid.String() ··· 41 44 } 42 45 43 46 var blockCtResp CountResp 44 - if err := s.db.Raw("SELECT COUNT(*) AS ct FROM blocks WHERE did = ?", nil, urepo.Repo.Did).Scan(&blockCtResp).Error; err != nil { 45 - s.logger.Error("error getting block count", "error", err) 47 + if err := s.db.Raw(ctx, "SELECT COUNT(*) AS ct FROM blocks WHERE did = ?", nil, urepo.Repo.Did).Scan(&blockCtResp).Error; err != nil { 48 + logger.Error("error getting block count", "error", err) 46 49 return helpers.ServerError(e, nil) 47 50 } 48 51 resp.RepoBlocks = blockCtResp.Ct 49 52 50 53 var recCtResp CountResp 51 - if err := s.db.Raw("SELECT COUNT(*) AS ct FROM records WHERE did = ?", nil, urepo.Repo.Did).Scan(&recCtResp).Error; err != nil { 52 - s.logger.Error("error getting record count", "error", err) 54 + if err := s.db.Raw(ctx, "SELECT COUNT(*) AS ct FROM records WHERE did = ?", nil, urepo.Repo.Did).Scan(&recCtResp).Error; err != nil { 55 + logger.Error("error getting record count", "error", err) 53 56 return helpers.ServerError(e, nil) 54 57 } 55 58 resp.IndexedRecords = recCtResp.Ct 56 59 57 60 var blobCtResp CountResp 58 - if err := s.db.Raw("SELECT COUNT(*) AS ct FROM blobs WHERE did = ?", nil, urepo.Repo.Did).Scan(&blobCtResp).Error; err != nil { 59 - s.logger.Error("error getting record count", "error", err) 61 + if err := s.db.Raw(ctx, "SELECT COUNT(*) AS ct FROM blobs WHERE did = ?", nil, urepo.Repo.Did).Scan(&blobCtResp).Error; err != nil { 62 + logger.Error("error getting record count", "error", err) 60 63 return helpers.ServerError(e, nil) 61 64 } 62 65 resp.ExpectedBlobs = blobCtResp.Ct
+6 -3
server/handle_server_confirm_email.go
··· 15 15 } 16 16 17 17 func (s *Server) handleServerConfirmEmail(e echo.Context) error { 18 + ctx := e.Request().Context() 19 + logger := s.logger.With("name", "handleServerConfirmEmail") 20 + 18 21 urepo := e.Get("repo").(*models.RepoActor) 19 22 20 23 var req ComAtprotoServerConfirmEmailRequest 21 24 if err := e.Bind(&req); err != nil { 22 - s.logger.Error("error binding", "error", err) 25 + logger.Error("error binding", "error", err) 23 26 return helpers.ServerError(e, nil) 24 27 } 25 28 ··· 41 44 42 45 now := time.Now().UTC() 43 46 44 - if err := s.db.Exec("UPDATE repos SET email_verification_code = NULL, email_verification_code_expires_at = NULL, email_confirmed_at = ? WHERE did = ?", nil, now, urepo.Repo.Did).Error; err != nil { 45 - s.logger.Error("error updating user", "error", err) 47 + if err := s.db.Exec(ctx, "UPDATE repos SET email_verification_code = NULL, email_verification_code_expires_at = NULL, email_confirmed_at = ? WHERE did = ?", nil, now, urepo.Repo.Did).Error; err != nil { 48 + logger.Error("error updating user", "error", err) 46 49 return helpers.ServerError(e, nil) 47 50 } 48 51
+54 -43
server/handle_server_create_account.go
··· 25 25 Handle string `json:"handle" validate:"required,atproto-handle"` 26 26 Did *string `json:"did" validate:"atproto-did"` 27 27 Password string `json:"password" validate:"required"` 28 - InviteCode string `json:"inviteCode" validate:"required"` 28 + InviteCode string `json:"inviteCode" validate:"omitempty"` 29 29 } 30 30 31 31 type ComAtprotoServerCreateAccountResponse struct { ··· 36 36 } 37 37 38 38 func (s *Server) handleCreateAccount(e echo.Context) error { 39 + ctx := e.Request().Context() 40 + logger := s.logger.With("name", "handleServerCreateAccount") 41 + 39 42 var request ComAtprotoServerCreateAccountRequest 40 43 41 44 if err := e.Bind(&request); err != nil { 42 - s.logger.Error("error receiving request", "endpoint", "com.atproto.server.createAccount", "error", err) 45 + logger.Error("error receiving request", "endpoint", "com.atproto.server.createAccount", "error", err) 43 46 return helpers.ServerError(e, nil) 44 47 } 45 48 46 49 request.Handle = strings.ToLower(request.Handle) 47 50 48 51 if err := e.Validate(request); err != nil { 49 - s.logger.Error("error validating request", "endpoint", "com.atproto.server.createAccount", "error", err) 52 + logger.Error("error validating request", "endpoint", "com.atproto.server.createAccount", "error", err) 50 53 51 54 var verr ValidationError 52 55 if errors.As(err, &verr) { ··· 68 71 } 69 72 } 70 73 } 71 - 74 + 72 75 var signupDid string 73 76 if request.Did != nil { 74 - signupDid = *request.Did; 75 - 77 + signupDid = *request.Did 78 + 76 79 token := strings.TrimSpace(strings.Replace(e.Request().Header.Get("authorization"), "Bearer ", "", 1)) 77 80 if token == "" { 78 81 return helpers.UnauthorizedError(e, to.StringPtr("must authenticate to use an existing did")) ··· 80 83 authDid, err := s.validateServiceAuth(e.Request().Context(), token, "com.atproto.server.createAccount") 81 84 82 85 if err != nil { 83 - s.logger.Warn("error validating authorization token", "endpoint", "com.atproto.server.createAccount", "error", err) 86 + logger.Warn("error validating authorization token", "endpoint", "com.atproto.server.createAccount", "error", err) 84 87 return helpers.UnauthorizedError(e, to.StringPtr("invalid authorization token")) 85 88 } 86 89 ··· 90 93 } 91 94 92 95 // see if the handle is already taken 93 - actor, err := s.getActorByHandle(request.Handle) 96 + actor, err := s.getActorByHandle(ctx, request.Handle) 94 97 if err != nil && err != gorm.ErrRecordNotFound { 95 - s.logger.Error("error looking up handle in db", "endpoint", "com.atproto.server.createAccount", "error", err) 98 + logger.Error("error looking up handle in db", "endpoint", "com.atproto.server.createAccount", "error", err) 96 99 return helpers.ServerError(e, nil) 97 100 } 98 101 if err == nil && actor.Did != signupDid { ··· 104 107 } 105 108 106 109 var ic models.InviteCode 107 - if err := s.db.Raw("SELECT * FROM invite_codes WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil { 108 - if err == gorm.ErrRecordNotFound { 110 + if s.config.RequireInvite { 111 + if strings.TrimSpace(request.InviteCode) == "" { 109 112 return helpers.InputError(e, to.StringPtr("InvalidInviteCode")) 110 113 } 111 - s.logger.Error("error getting invite code from db", "error", err) 112 - return helpers.ServerError(e, nil) 113 - } 114 114 115 - if ic.RemainingUseCount < 1 { 116 - return helpers.InputError(e, to.StringPtr("InvalidInviteCode")) 115 + if err := s.db.Raw(ctx, "SELECT * FROM invite_codes WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil { 116 + if err == gorm.ErrRecordNotFound { 117 + return helpers.InputError(e, to.StringPtr("InvalidInviteCode")) 118 + } 119 + logger.Error("error getting invite code from db", "error", err) 120 + return helpers.ServerError(e, nil) 121 + } 122 + 123 + if ic.RemainingUseCount < 1 { 124 + return helpers.InputError(e, to.StringPtr("InvalidInviteCode")) 125 + } 117 126 } 118 127 119 128 // see if the email is already taken 120 - existingRepo, err := s.getRepoByEmail(request.Email) 129 + existingRepo, err := s.getRepoByEmail(ctx, request.Email) 121 130 if err != nil && err != gorm.ErrRecordNotFound { 122 - s.logger.Error("error looking up email in db", "endpoint", "com.atproto.server.createAccount", "error", err) 131 + logger.Error("error looking up email in db", "endpoint", "com.atproto.server.createAccount", "error", err) 123 132 return helpers.ServerError(e, nil) 124 133 } 125 134 if err == nil && existingRepo.Did != signupDid { ··· 131 140 var k *atcrypto.PrivateKeyK256 132 141 133 142 if signupDid != "" { 134 - reservedKey, err := s.getReservedKey(signupDid) 143 + reservedKey, err := s.getReservedKey(ctx, signupDid) 135 144 if err != nil { 136 - s.logger.Error("error looking up reserved key", "error", err) 145 + logger.Error("error looking up reserved key", "error", err) 137 146 } 138 147 if reservedKey != nil { 139 148 k, err = atcrypto.ParsePrivateBytesK256(reservedKey.PrivateKey) 140 149 if err != nil { 141 - s.logger.Error("error parsing reserved key", "error", err) 150 + logger.Error("error parsing reserved key", "error", err) 142 151 k = nil 143 152 } else { 144 153 defer func() { 145 - if delErr := s.deleteReservedKey(reservedKey.KeyDid, reservedKey.Did); delErr != nil { 146 - s.logger.Error("error deleting reserved key", "error", delErr) 154 + if delErr := s.deleteReservedKey(ctx, reservedKey.KeyDid, reservedKey.Did); delErr != nil { 155 + logger.Error("error deleting reserved key", "error", delErr) 147 156 } 148 157 }() 149 158 } ··· 153 162 if k == nil { 154 163 k, err = atcrypto.GeneratePrivateKeyK256() 155 164 if err != nil { 156 - s.logger.Error("error creating signing key", "endpoint", "com.atproto.server.createAccount", "error", err) 165 + logger.Error("error creating signing key", "endpoint", "com.atproto.server.createAccount", "error", err) 157 166 return helpers.ServerError(e, nil) 158 167 } 159 168 } ··· 161 170 if signupDid == "" { 162 171 did, op, err := s.plcClient.CreateDID(k, "", request.Handle) 163 172 if err != nil { 164 - s.logger.Error("error creating operation", "endpoint", "com.atproto.server.createAccount", "error", err) 173 + logger.Error("error creating operation", "endpoint", "com.atproto.server.createAccount", "error", err) 165 174 return helpers.ServerError(e, nil) 166 175 } 167 176 168 177 if err := s.plcClient.SendOperation(e.Request().Context(), did, op); err != nil { 169 - s.logger.Error("error sending plc op", "endpoint", "com.atproto.server.createAccount", "error", err) 178 + logger.Error("error sending plc op", "endpoint", "com.atproto.server.createAccount", "error", err) 170 179 return helpers.ServerError(e, nil) 171 180 } 172 181 signupDid = did ··· 174 183 175 184 hashed, err := bcrypt.GenerateFromPassword([]byte(request.Password), 10) 176 185 if err != nil { 177 - s.logger.Error("error hashing password", "error", err) 186 + logger.Error("error hashing password", "error", err) 178 187 return helpers.ServerError(e, nil) 179 188 } 180 189 ··· 193 202 Handle: request.Handle, 194 203 } 195 204 196 - if err := s.db.Create(&urepo, nil).Error; err != nil { 197 - s.logger.Error("error inserting new repo", "error", err) 205 + if err := s.db.Create(ctx, &urepo, nil).Error; err != nil { 206 + logger.Error("error inserting new repo", "error", err) 198 207 return helpers.ServerError(e, nil) 199 208 } 200 - 201 - if err := s.db.Create(&actor, nil).Error; err != nil { 202 - s.logger.Error("error inserting new actor", "error", err) 209 + 210 + if err := s.db.Create(ctx, &actor, nil).Error; err != nil { 211 + logger.Error("error inserting new actor", "error", err) 203 212 return helpers.ServerError(e, nil) 204 213 } 205 214 } else { 206 - if err := s.db.Save(&actor, nil).Error; err != nil { 207 - s.logger.Error("error inserting new actor", "error", err) 215 + if err := s.db.Save(ctx, &actor, nil).Error; err != nil { 216 + logger.Error("error inserting new actor", "error", err) 208 217 return helpers.ServerError(e, nil) 209 218 } 210 219 } ··· 215 224 216 225 root, rev, err := r.Commit(context.TODO(), urepo.SignFor) 217 226 if err != nil { 218 - s.logger.Error("error committing", "error", err) 227 + logger.Error("error committing", "error", err) 219 228 return helpers.ServerError(e, nil) 220 229 } 221 230 222 231 if err := s.UpdateRepo(context.TODO(), urepo.Did, root, rev); err != nil { 223 - s.logger.Error("error updating repo after commit", "error", err) 232 + logger.Error("error updating repo after commit", "error", err) 224 233 return helpers.ServerError(e, nil) 225 234 } 226 235 ··· 234 243 }) 235 244 } 236 245 237 - if err := s.db.Raw("UPDATE invite_codes SET remaining_use_count = remaining_use_count - 1 WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil { 238 - s.logger.Error("error decrementing use count", "error", err) 239 - return helpers.ServerError(e, nil) 246 + if s.config.RequireInvite { 247 + if err := s.db.Raw(ctx, "UPDATE invite_codes SET remaining_use_count = remaining_use_count - 1 WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil { 248 + logger.Error("error decrementing use count", "error", err) 249 + return helpers.ServerError(e, nil) 250 + } 240 251 } 241 252 242 - sess, err := s.createSession(&urepo) 253 + sess, err := s.createSession(ctx, &urepo) 243 254 if err != nil { 244 - s.logger.Error("error creating new session", "error", err) 255 + logger.Error("error creating new session", "error", err) 245 256 return helpers.ServerError(e, nil) 246 257 } 247 258 248 259 go func() { 249 260 if err := s.sendEmailVerification(urepo.Email, actor.Handle, *urepo.EmailVerificationCode); err != nil { 250 - s.logger.Error("error sending email verification email", "error", err) 261 + logger.Error("error sending email verification email", "error", err) 251 262 } 252 263 if err := s.sendWelcomeMail(urepo.Email, actor.Handle); err != nil { 253 - s.logger.Error("error sending welcome email", "error", err) 264 + logger.Error("error sending welcome email", "error", err) 254 265 } 255 266 }() 256 267
+7 -4
server/handle_server_create_invite_code.go
··· 17 17 } 18 18 19 19 func (s *Server) handleCreateInviteCode(e echo.Context) error { 20 + ctx := e.Request().Context() 21 + logger := s.logger.With("name", "handleServerCreateInviteCode") 22 + 20 23 var req ComAtprotoServerCreateInviteCodeRequest 21 24 if err := e.Bind(&req); err != nil { 22 - s.logger.Error("error binding", "error", err) 25 + logger.Error("error binding", "error", err) 23 26 return helpers.ServerError(e, nil) 24 27 } 25 28 26 29 if err := e.Validate(req); err != nil { 27 - s.logger.Error("error validating", "error", err) 30 + logger.Error("error validating", "error", err) 28 31 return helpers.InputError(e, nil) 29 32 } 30 33 ··· 37 40 acc = *req.ForAccount 38 41 } 39 42 40 - if err := s.db.Create(&models.InviteCode{ 43 + if err := s.db.Create(ctx, &models.InviteCode{ 41 44 Code: ic, 42 45 Did: acc, 43 46 RemainingUseCount: req.UseCount, 44 47 }, nil).Error; err != nil { 45 - s.logger.Error("error creating invite code", "error", err) 48 + logger.Error("error creating invite code", "error", err) 46 49 return helpers.ServerError(e, nil) 47 50 } 48 51
+7 -4
server/handle_server_create_invite_codes.go
··· 22 22 } 23 23 24 24 func (s *Server) handleCreateInviteCodes(e echo.Context) error { 25 + ctx := e.Request().Context() 26 + logger := s.logger.With("name", "handleServerCreateInviteCodes") 27 + 25 28 var req ComAtprotoServerCreateInviteCodesRequest 26 29 if err := e.Bind(&req); err != nil { 27 - s.logger.Error("error binding", "error", err) 30 + logger.Error("error binding", "error", err) 28 31 return helpers.ServerError(e, nil) 29 32 } 30 33 31 34 if err := e.Validate(req); err != nil { 32 - s.logger.Error("error validating", "error", err) 35 + logger.Error("error validating", "error", err) 33 36 return helpers.InputError(e, nil) 34 37 } 35 38 ··· 50 53 ic := uuid.NewString() 51 54 ics = append(ics, ic) 52 55 53 - if err := s.db.Create(&models.InviteCode{ 56 + if err := s.db.Create(ctx, &models.InviteCode{ 54 57 Code: ic, 55 58 Did: did, 56 59 RemainingUseCount: req.UseCount, 57 60 }, nil).Error; err != nil { 58 - s.logger.Error("error creating invite code", "error", err) 61 + logger.Error("error creating invite code", "error", err) 59 62 return helpers.ServerError(e, nil) 60 63 } 61 64 }
+65 -9
server/handle_server_create_session.go
··· 1 1 package server 2 2 3 3 import ( 4 + "context" 4 5 "errors" 6 + "fmt" 5 7 "strings" 8 + "time" 6 9 7 10 "github.com/Azure/go-autorest/autorest/to" 8 11 "github.com/bluesky-social/indigo/atproto/syntax" ··· 32 35 } 33 36 34 37 func (s *Server) handleCreateSession(e echo.Context) error { 38 + ctx := e.Request().Context() 39 + logger := s.logger.With("name", "handleServerCreateSession") 40 + 35 41 var req ComAtprotoServerCreateSessionRequest 36 42 if err := e.Bind(&req); err != nil { 37 - s.logger.Error("error binding request", "endpoint", "com.atproto.server.serverCreateSession", "error", err) 43 + logger.Error("error binding request", "endpoint", "com.atproto.server.serverCreateSession", "error", err) 38 44 return helpers.ServerError(e, nil) 39 45 } 40 46 ··· 65 71 var err error 66 72 switch idtype { 67 73 case "did": 68 - err = s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", nil, req.Identifier).Scan(&repo).Error 74 + err = s.db.Raw(ctx, "SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", nil, req.Identifier).Scan(&repo).Error 69 75 case "handle": 70 - err = s.db.Raw("SELECT r.*, a.* FROM actors a LEFT JOIN repos r ON a.did = r.did WHERE a.handle = ?", nil, req.Identifier).Scan(&repo).Error 76 + err = s.db.Raw(ctx, "SELECT r.*, a.* FROM actors a LEFT JOIN repos r ON a.did = r.did WHERE a.handle = ?", nil, req.Identifier).Scan(&repo).Error 71 77 case "email": 72 - err = s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.email = ?", nil, req.Identifier).Scan(&repo).Error 78 + err = s.db.Raw(ctx, "SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.email = ?", nil, req.Identifier).Scan(&repo).Error 73 79 } 74 80 75 81 if err != nil { ··· 77 83 return helpers.InputError(e, to.StringPtr("InvalidRequest")) 78 84 } 79 85 80 - s.logger.Error("erorr looking up repo", "endpoint", "com.atproto.server.createSession", "error", err) 86 + logger.Error("erorr looking up repo", "endpoint", "com.atproto.server.createSession", "error", err) 81 87 return helpers.ServerError(e, nil) 82 88 } 83 89 84 90 if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil { 85 91 if err != bcrypt.ErrMismatchedHashAndPassword { 86 - s.logger.Error("erorr comparing hash and password", "error", err) 92 + logger.Error("erorr comparing hash and password", "error", err) 87 93 } 88 94 return helpers.InputError(e, to.StringPtr("InvalidRequest")) 89 95 } 90 96 91 - sess, err := s.createSession(&repo.Repo) 97 + // if repo requires 2FA token and one hasn't been provided, return error prompting for one 98 + if repo.TwoFactorType != models.TwoFactorTypeNone && (req.AuthFactorToken == nil || *req.AuthFactorToken == "") { 99 + err = s.createAndSendTwoFactorCode(ctx, repo) 100 + if err != nil { 101 + logger.Error("sending 2FA code", "error", err) 102 + return helpers.ServerError(e, nil) 103 + } 104 + 105 + return helpers.InputError(e, to.StringPtr("AuthFactorTokenRequired")) 106 + } 107 + 108 + // if 2FA is required, now check that the one provided is valid 109 + if repo.TwoFactorType != models.TwoFactorTypeNone { 110 + if repo.TwoFactorCode == nil || repo.TwoFactorCodeExpiresAt == nil { 111 + err = s.createAndSendTwoFactorCode(ctx, repo) 112 + if err != nil { 113 + logger.Error("sending 2FA code", "error", err) 114 + return helpers.ServerError(e, nil) 115 + } 116 + 117 + return helpers.InputError(e, to.StringPtr("AuthFactorTokenRequired")) 118 + } 119 + 120 + if *repo.TwoFactorCode != *req.AuthFactorToken { 121 + return helpers.InvalidTokenError(e) 122 + } 123 + 124 + if time.Now().UTC().After(*repo.TwoFactorCodeExpiresAt) { 125 + return helpers.ExpiredTokenError(e) 126 + } 127 + } 128 + 129 + sess, err := s.createSession(ctx, &repo.Repo) 92 130 if err != nil { 93 - s.logger.Error("error creating session", "error", err) 131 + logger.Error("error creating session", "error", err) 94 132 return helpers.ServerError(e, nil) 95 133 } 96 134 ··· 101 139 Did: repo.Repo.Did, 102 140 Email: repo.Email, 103 141 EmailConfirmed: repo.EmailConfirmedAt != nil, 104 - EmailAuthFactor: false, 142 + EmailAuthFactor: repo.TwoFactorType != models.TwoFactorTypeNone, 105 143 Active: repo.Active(), 106 144 Status: repo.Status(), 107 145 }) 108 146 } 147 + 148 + func (s *Server) createAndSendTwoFactorCode(ctx context.Context, repo models.RepoActor) error { 149 + // TODO: when implementing a new type of 2FA there should be some logic in here to send the 150 + // right type of code 151 + 152 + code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 153 + eat := time.Now().Add(10 * time.Minute).UTC() 154 + 155 + if err := s.db.Exec(ctx, "UPDATE repos SET two_factor_code = ?, two_factor_code_expires_at = ? WHERE did = ?", nil, code, eat, repo.Repo.Did).Error; err != nil { 156 + return fmt.Errorf("updating repo: %w", err) 157 + } 158 + 159 + if err := s.sendTwoFactorCode(repo.Email, repo.Handle, code); err != nil { 160 + return fmt.Errorf("sending email: %w", err) 161 + } 162 + 163 + return nil 164 + }
+6 -3
server/handle_server_deactivate_account.go
··· 19 19 } 20 20 21 21 func (s *Server) handleServerDeactivateAccount(e echo.Context) error { 22 + ctx := e.Request().Context() 23 + logger := s.logger.With("name", "handleServerDeactivateAccount") 24 + 22 25 var req ComAtprotoServerDeactivateAccountRequest 23 26 if err := e.Bind(&req); err != nil { 24 - s.logger.Error("error binding", "error", err) 27 + logger.Error("error binding", "error", err) 25 28 return helpers.ServerError(e, nil) 26 29 } 27 30 28 31 urepo := e.Get("repo").(*models.RepoActor) 29 32 30 - if err := s.db.Exec("UPDATE repos SET deactivated = ? WHERE did = ?", nil, true, urepo.Repo.Did).Error; err != nil { 31 - s.logger.Error("error updating account status to deactivated", "error", err) 33 + if err := s.db.Exec(ctx, "UPDATE repos SET deactivated = ? WHERE did = ?", nil, true, urepo.Repo.Did).Error; err != nil { 34 + logger.Error("error updating account status to deactivated", "error", err) 32 35 return helpers.ServerError(e, nil) 33 36 } 34 37
+51 -26
server/handle_server_delete_account.go
··· 20 20 } 21 21 22 22 func (s *Server) handleServerDeleteAccount(e echo.Context) error { 23 + ctx := e.Request().Context() 24 + logger := s.logger.With("name", "handleServerDeleteAccount") 25 + 23 26 var req ComAtprotoServerDeleteAccountRequest 24 27 if err := e.Bind(&req); err != nil { 25 - s.logger.Error("error binding", "error", err) 28 + logger.Error("error binding", "error", err) 26 29 return helpers.ServerError(e, nil) 27 30 } 28 31 29 32 if err := e.Validate(&req); err != nil { 30 - s.logger.Error("error validating", "error", err) 33 + logger.Error("error validating", "error", err) 31 34 return helpers.ServerError(e, nil) 32 35 } 33 36 34 - urepo, err := s.getRepoActorByDid(req.Did) 37 + urepo, err := s.getRepoActorByDid(ctx, req.Did) 35 38 if err != nil { 36 - s.logger.Error("error getting repo", "error", err) 39 + logger.Error("error getting repo", "error", err) 37 40 return echo.NewHTTPError(400, "account not found") 38 41 } 39 42 40 43 if err := bcrypt.CompareHashAndPassword([]byte(urepo.Repo.Password), []byte(req.Password)); err != nil { 41 - s.logger.Error("password mismatch", "error", err) 44 + logger.Error("password mismatch", "error", err) 42 45 return echo.NewHTTPError(401, "Invalid did or password") 43 46 } 44 47 45 48 if urepo.Repo.AccountDeleteCode == nil || urepo.Repo.AccountDeleteCodeExpiresAt == nil { 46 - s.logger.Error("no deletion token found for account") 49 + logger.Error("no deletion token found for account") 47 50 return echo.NewHTTPError(400, map[string]interface{}{ 48 51 "error": "InvalidToken", 49 52 "message": "Token is invalid", ··· 51 54 } 52 55 53 56 if *urepo.Repo.AccountDeleteCode != req.Token { 54 - s.logger.Error("deletion token mismatch") 57 + logger.Error("deletion token mismatch") 55 58 return echo.NewHTTPError(400, map[string]interface{}{ 56 59 "error": "InvalidToken", 57 60 "message": "Token is invalid", ··· 59 62 } 60 63 61 64 if time.Now().UTC().After(*urepo.Repo.AccountDeleteCodeExpiresAt) { 62 - s.logger.Error("deletion token expired") 65 + logger.Error("deletion token expired") 63 66 return echo.NewHTTPError(400, map[string]interface{}{ 64 67 "error": "ExpiredToken", 65 68 "message": "Token is expired", 66 69 }) 67 70 } 68 71 69 - if err := s.db.Exec("DELETE FROM blocks WHERE did = ?", nil, req.Did).Error; err != nil { 70 - s.logger.Error("error deleting blocks", "error", err) 72 + tx := s.db.Begin(ctx) 73 + if tx.Error != nil { 74 + logger.Error("error starting transaction", "error", tx.Error) 75 + return helpers.ServerError(e, nil) 76 + } 77 + 78 + status := "error" 79 + func() { 80 + if status == "error" { 81 + if err := tx.Rollback().Error; err != nil { 82 + logger.Error("error rolling back after delete failure", "err", err) 83 + } 84 + } 85 + }() 86 + 87 + if err := tx.Exec("DELETE FROM blocks WHERE did = ?", nil, req.Did).Error; err != nil { 88 + logger.Error("error deleting blocks", "error", err) 89 + return helpers.ServerError(e, nil) 90 + } 91 + 92 + if err := tx.Exec("DELETE FROM records WHERE did = ?", nil, req.Did).Error; err != nil { 93 + logger.Error("error deleting records", "error", err) 71 94 return helpers.ServerError(e, nil) 72 95 } 73 96 74 - if err := s.db.Exec("DELETE FROM records WHERE did = ?", nil, req.Did).Error; err != nil { 75 - s.logger.Error("error deleting records", "error", err) 97 + if err := tx.Exec("DELETE FROM blobs WHERE did = ?", nil, req.Did).Error; err != nil { 98 + logger.Error("error deleting blobs", "error", err) 76 99 return helpers.ServerError(e, nil) 77 100 } 78 101 79 - if err := s.db.Exec("DELETE FROM blobs WHERE did = ?", nil, req.Did).Error; err != nil { 80 - s.logger.Error("error deleting blobs", "error", err) 102 + if err := tx.Exec("DELETE FROM tokens WHERE did = ?", nil, req.Did).Error; err != nil { 103 + logger.Error("error deleting tokens", "error", err) 81 104 return helpers.ServerError(e, nil) 82 105 } 83 106 84 - if err := s.db.Exec("DELETE FROM tokens WHERE did = ?", nil, req.Did).Error; err != nil { 85 - s.logger.Error("error deleting tokens", "error", err) 107 + if err := tx.Exec("DELETE FROM refresh_tokens WHERE did = ?", nil, req.Did).Error; err != nil { 108 + logger.Error("error deleting refresh tokens", "error", err) 86 109 return helpers.ServerError(e, nil) 87 110 } 88 111 89 - if err := s.db.Exec("DELETE FROM refresh_tokens WHERE did = ?", nil, req.Did).Error; err != nil { 90 - s.logger.Error("error deleting refresh tokens", "error", err) 112 + if err := tx.Exec("DELETE FROM reserved_keys WHERE did = ?", nil, req.Did).Error; err != nil { 113 + logger.Error("error deleting reserved keys", "error", err) 91 114 return helpers.ServerError(e, nil) 92 115 } 93 116 94 - if err := s.db.Exec("DELETE FROM reserved_keys WHERE did = ?", nil, req.Did).Error; err != nil { 95 - s.logger.Error("error deleting reserved keys", "error", err) 117 + if err := tx.Exec("DELETE FROM invite_codes WHERE did = ?", nil, req.Did).Error; err != nil { 118 + logger.Error("error deleting invite codes", "error", err) 96 119 return helpers.ServerError(e, nil) 97 120 } 98 121 99 - if err := s.db.Exec("DELETE FROM invite_codes WHERE did = ?", nil, req.Did).Error; err != nil { 100 - s.logger.Error("error deleting invite codes", "error", err) 122 + if err := tx.Exec("DELETE FROM actors WHERE did = ?", nil, req.Did).Error; err != nil { 123 + logger.Error("error deleting actor", "error", err) 101 124 return helpers.ServerError(e, nil) 102 125 } 103 126 104 - if err := s.db.Exec("DELETE FROM actors WHERE did = ?", nil, req.Did).Error; err != nil { 105 - s.logger.Error("error deleting actor", "error", err) 127 + if err := tx.Exec("DELETE FROM repos WHERE did = ?", nil, req.Did).Error; err != nil { 128 + logger.Error("error deleting repo", "error", err) 106 129 return helpers.ServerError(e, nil) 107 130 } 108 131 109 - if err := s.db.Exec("DELETE FROM repos WHERE did = ?", nil, req.Did).Error; err != nil { 110 - s.logger.Error("error deleting repo", "error", err) 132 + status = "ok" 133 + 134 + if err := tx.Commit().Error; err != nil { 135 + logger.Error("error committing transaction", "error", err) 111 136 return helpers.ServerError(e, nil) 112 137 } 113 138
+4 -2
server/handle_server_delete_session.go
··· 7 7 ) 8 8 9 9 func (s *Server) handleDeleteSession(e echo.Context) error { 10 + ctx := e.Request().Context() 11 + 10 12 token := e.Get("token").(string) 11 13 12 14 var acctok models.Token 13 - if err := s.db.Raw("DELETE FROM tokens WHERE token = ? RETURNING *", nil, token).Scan(&acctok).Error; err != nil { 15 + if err := s.db.Raw(ctx, "DELETE FROM tokens WHERE token = ? RETURNING *", nil, token).Scan(&acctok).Error; err != nil { 14 16 s.logger.Error("error deleting access token from db", "error", err) 15 17 return helpers.ServerError(e, nil) 16 18 } 17 19 18 - if err := s.db.Exec("DELETE FROM refresh_tokens WHERE token = ?", nil, acctok.RefreshToken).Error; err != nil { 20 + if err := s.db.Exec(ctx, "DELETE FROM refresh_tokens WHERE token = ?", nil, acctok.RefreshToken).Error; err != nil { 19 21 s.logger.Error("error deleting refresh token from db", "error", err) 20 22 return helpers.ServerError(e, nil) 21 23 }
+1 -1
server/handle_server_describe_server.go
··· 22 22 23 23 func (s *Server) handleDescribeServer(e echo.Context) error { 24 24 return e.JSON(200, ComAtprotoServerDescribeServerResponse{ 25 - InviteCodeRequired: true, 25 + InviteCodeRequired: s.config.RequireInvite, 26 26 PhoneVerificationRequired: false, 27 27 AvailableUserDomains: []string{"." + s.config.Hostname}, // TODO: more 28 28 Links: ComAtprotoServerDescribeServerResponseLinks{
+7 -5
server/handle_server_get_service_auth.go
··· 25 25 } 26 26 27 27 func (s *Server) handleServerGetServiceAuth(e echo.Context) error { 28 + logger := s.logger.With("name", "handleServerGetServiceAuth") 29 + 28 30 var req ServerGetServiceAuthRequest 29 31 if err := e.Bind(&req); err != nil { 30 - s.logger.Error("could not bind service auth request", "error", err) 32 + logger.Error("could not bind service auth request", "error", err) 31 33 return helpers.ServerError(e, nil) 32 34 } 33 35 ··· 64 66 } 65 67 hj, err := json.Marshal(header) 66 68 if err != nil { 67 - s.logger.Error("error marshaling header", "error", err) 69 + logger.Error("error marshaling header", "error", err) 68 70 return helpers.ServerError(e, nil) 69 71 } 70 72 ··· 82 84 } 83 85 pj, err := json.Marshal(payload) 84 86 if err != nil { 85 - s.logger.Error("error marashaling payload", "error", err) 87 + logger.Error("error marashaling payload", "error", err) 86 88 return helpers.ServerError(e, nil) 87 89 } 88 90 ··· 93 95 94 96 sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey) 95 97 if err != nil { 96 - s.logger.Error("can't load private key", "error", err) 98 + logger.Error("can't load private key", "error", err) 97 99 return err 98 100 } 99 101 100 102 R, S, _, err := sk.SignRaw(rand.Reader, hash[:]) 101 103 if err != nil { 102 - s.logger.Error("error signing", "error", err) 104 + logger.Error("error signing", "error", err) 103 105 return helpers.ServerError(e, nil) 104 106 } 105 107
+1 -1
server/handle_server_get_session.go
··· 23 23 Did: repo.Repo.Did, 24 24 Email: repo.Email, 25 25 EmailConfirmed: repo.EmailConfirmedAt != nil, 26 - EmailAuthFactor: false, // TODO: todo todo 26 + EmailAuthFactor: repo.TwoFactorType != models.TwoFactorTypeNone, 27 27 Active: repo.Active(), 28 28 Status: repo.Status(), 29 29 })
+9 -6
server/handle_server_refresh_session.go
··· 16 16 } 17 17 18 18 func (s *Server) handleRefreshSession(e echo.Context) error { 19 + ctx := e.Request().Context() 20 + logger := s.logger.With("name", "handleServerRefreshSession") 21 + 19 22 token := e.Get("token").(string) 20 23 repo := e.Get("repo").(*models.RepoActor) 21 24 22 - if err := s.db.Exec("DELETE FROM refresh_tokens WHERE token = ?", nil, token).Error; err != nil { 23 - s.logger.Error("error getting refresh token from db", "error", err) 25 + if err := s.db.Exec(ctx, "DELETE FROM refresh_tokens WHERE token = ?", nil, token).Error; err != nil { 26 + logger.Error("error getting refresh token from db", "error", err) 24 27 return helpers.ServerError(e, nil) 25 28 } 26 29 27 - if err := s.db.Exec("DELETE FROM tokens WHERE refresh_token = ?", nil, token).Error; err != nil { 28 - s.logger.Error("error deleting access token from db", "error", err) 30 + if err := s.db.Exec(ctx, "DELETE FROM tokens WHERE refresh_token = ?", nil, token).Error; err != nil { 31 + logger.Error("error deleting access token from db", "error", err) 29 32 return helpers.ServerError(e, nil) 30 33 } 31 34 32 - sess, err := s.createSession(&repo.Repo) 35 + sess, err := s.createSession(ctx, &repo.Repo) 33 36 if err != nil { 34 - s.logger.Error("error creating new session for refresh", "error", err) 37 + logger.Error("error creating new session for refresh", "error", err) 35 38 return helpers.ServerError(e, nil) 36 39 } 37 40
+6 -3
server/handle_server_request_account_delete.go
··· 10 10 ) 11 11 12 12 func (s *Server) handleServerRequestAccountDelete(e echo.Context) error { 13 + ctx := e.Request().Context() 14 + logger := s.logger.With("name", "handleServerRequestAccountDelete") 15 + 13 16 urepo := e.Get("repo").(*models.RepoActor) 14 17 15 18 token := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 16 19 expiresAt := time.Now().UTC().Add(15 * time.Minute) 17 20 18 - if err := s.db.Exec("UPDATE repos SET account_delete_code = ?, account_delete_code_expires_at = ? WHERE did = ?", nil, token, expiresAt, urepo.Repo.Did).Error; err != nil { 19 - s.logger.Error("error setting deletion token", "error", err) 21 + if err := s.db.Exec(ctx, "UPDATE repos SET account_delete_code = ?, account_delete_code_expires_at = ? WHERE did = ?", nil, token, expiresAt, urepo.Repo.Did).Error; err != nil { 22 + logger.Error("error setting deletion token", "error", err) 20 23 return helpers.ServerError(e, nil) 21 24 } 22 25 23 26 if urepo.Email != "" { 24 27 if err := s.sendAccountDeleteEmail(urepo.Email, urepo.Actor.Handle, token); err != nil { 25 - s.logger.Error("error sending account deletion email", "error", err) 28 + logger.Error("error sending account deletion email", "error", err) 26 29 } 27 30 } 28 31
+6 -3
server/handle_server_request_email_confirmation.go
··· 11 11 ) 12 12 13 13 func (s *Server) handleServerRequestEmailConfirmation(e echo.Context) error { 14 + ctx := e.Request().Context() 15 + logger := s.logger.With("name", "handleServerRequestEmailConfirm") 16 + 14 17 urepo := e.Get("repo").(*models.RepoActor) 15 18 16 19 if urepo.EmailConfirmedAt != nil { ··· 20 23 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 21 24 eat := time.Now().Add(10 * time.Minute).UTC() 22 25 23 - if err := s.db.Exec("UPDATE repos SET email_verification_code = ?, email_verification_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 24 - s.logger.Error("error updating user", "error", err) 26 + if err := s.db.Exec(ctx, "UPDATE repos SET email_verification_code = ?, email_verification_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 27 + logger.Error("error updating user", "error", err) 25 28 return helpers.ServerError(e, nil) 26 29 } 27 30 28 31 if err := s.sendEmailVerification(urepo.Email, urepo.Handle, code); err != nil { 29 - s.logger.Error("error sending mail", "error", err) 32 + logger.Error("error sending mail", "error", err) 30 33 return helpers.ServerError(e, nil) 31 34 } 32 35
+6 -3
server/handle_server_request_email_update.go
··· 14 14 } 15 15 16 16 func (s *Server) handleServerRequestEmailUpdate(e echo.Context) error { 17 + ctx := e.Request().Context() 18 + logger := s.logger.With("name", "handleServerRequestEmailUpdate") 19 + 17 20 urepo := e.Get("repo").(*models.RepoActor) 18 21 19 22 if urepo.EmailConfirmedAt != nil { 20 23 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 21 24 eat := time.Now().Add(10 * time.Minute).UTC() 22 25 23 - if err := s.db.Exec("UPDATE repos SET email_update_code = ?, email_update_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 24 - s.logger.Error("error updating repo", "error", err) 26 + if err := s.db.Exec(ctx, "UPDATE repos SET email_update_code = ?, email_update_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 27 + logger.Error("error updating repo", "error", err) 25 28 return helpers.ServerError(e, nil) 26 29 } 27 30 28 31 if err := s.sendEmailUpdate(urepo.Email, urepo.Handle, code); err != nil { 29 - s.logger.Error("error sending email", "error", err) 32 + logger.Error("error sending email", "error", err) 30 33 return helpers.ServerError(e, nil) 31 34 } 32 35 }
+7 -4
server/handle_server_request_password_reset.go
··· 14 14 } 15 15 16 16 func (s *Server) handleServerRequestPasswordReset(e echo.Context) error { 17 + ctx := e.Request().Context() 18 + logger := s.logger.With("name", "handleServerRequestPasswordReset") 19 + 17 20 urepo, ok := e.Get("repo").(*models.RepoActor) 18 21 if !ok { 19 22 var req ComAtprotoServerRequestPasswordResetRequest ··· 25 28 return err 26 29 } 27 30 28 - murepo, err := s.getRepoActorByEmail(req.Email) 31 + murepo, err := s.getRepoActorByEmail(ctx, req.Email) 29 32 if err != nil { 30 33 return err 31 34 } ··· 36 39 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 37 40 eat := time.Now().Add(10 * time.Minute).UTC() 38 41 39 - if err := s.db.Exec("UPDATE repos SET password_reset_code = ?, password_reset_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 40 - s.logger.Error("error updating repo", "error", err) 42 + if err := s.db.Exec(ctx, "UPDATE repos SET password_reset_code = ?, password_reset_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 43 + logger.Error("error updating repo", "error", err) 41 44 return helpers.ServerError(e, nil) 42 45 } 43 46 44 47 if err := s.sendPasswordReset(urepo.Email, urepo.Handle, code); err != nil { 45 - s.logger.Error("error sending email", "error", err) 48 + logger.Error("error sending email", "error", err) 46 49 return helpers.ServerError(e, nil) 47 50 } 48 51
+17 -13
server/handle_server_reserve_signing_key.go
··· 1 1 package server 2 2 3 3 import ( 4 + "context" 4 5 "time" 5 6 6 7 "github.com/bluesky-social/indigo/atproto/atcrypto" ··· 18 19 } 19 20 20 21 func (s *Server) handleServerReserveSigningKey(e echo.Context) error { 22 + ctx := e.Request().Context() 23 + logger := s.logger.With("name", "handleServerReserveSigningKey") 24 + 21 25 var req ServerReserveSigningKeyRequest 22 26 if err := e.Bind(&req); err != nil { 23 - s.logger.Error("could not bind reserve signing key request", "error", err) 27 + logger.Error("could not bind reserve signing key request", "error", err) 24 28 return helpers.ServerError(e, nil) 25 29 } 26 30 27 31 if req.Did != nil && *req.Did != "" { 28 32 var existing models.ReservedKey 29 - if err := s.db.Raw("SELECT * FROM reserved_keys WHERE did = ?", nil, *req.Did).Scan(&existing).Error; err == nil && existing.KeyDid != "" { 33 + if err := s.db.Raw(ctx, "SELECT * FROM reserved_keys WHERE did = ?", nil, *req.Did).Scan(&existing).Error; err == nil && existing.KeyDid != "" { 30 34 return e.JSON(200, ServerReserveSigningKeyResponse{ 31 35 SigningKey: existing.KeyDid, 32 36 }) ··· 35 39 36 40 k, err := atcrypto.GeneratePrivateKeyK256() 37 41 if err != nil { 38 - s.logger.Error("error creating signing key", "endpoint", "com.atproto.server.reserveSigningKey", "error", err) 42 + logger.Error("error creating signing key", "endpoint", "com.atproto.server.reserveSigningKey", "error", err) 39 43 return helpers.ServerError(e, nil) 40 44 } 41 45 42 46 pubKey, err := k.PublicKey() 43 47 if err != nil { 44 - s.logger.Error("error getting public key", "endpoint", "com.atproto.server.reserveSigningKey", "error", err) 48 + logger.Error("error getting public key", "endpoint", "com.atproto.server.reserveSigningKey", "error", err) 45 49 return helpers.ServerError(e, nil) 46 50 } 47 51 ··· 54 58 CreatedAt: time.Now(), 55 59 } 56 60 57 - if err := s.db.Create(&reservedKey, nil).Error; err != nil { 58 - s.logger.Error("error storing reserved key", "endpoint", "com.atproto.server.reserveSigningKey", "error", err) 61 + if err := s.db.Create(ctx, &reservedKey, nil).Error; err != nil { 62 + logger.Error("error storing reserved key", "endpoint", "com.atproto.server.reserveSigningKey", "error", err) 59 63 return helpers.ServerError(e, nil) 60 64 } 61 65 62 - s.logger.Info("reserved signing key", "keyDid", keyDid, "forDid", req.Did) 66 + logger.Info("reserved signing key", "keyDid", keyDid, "forDid", req.Did) 63 67 64 68 return e.JSON(200, ServerReserveSigningKeyResponse{ 65 69 SigningKey: keyDid, 66 70 }) 67 71 } 68 72 69 - func (s *Server) getReservedKey(keyDidOrDid string) (*models.ReservedKey, error) { 73 + func (s *Server) getReservedKey(ctx context.Context, keyDidOrDid string) (*models.ReservedKey, error) { 70 74 var reservedKey models.ReservedKey 71 75 72 - if err := s.db.Raw("SELECT * FROM reserved_keys WHERE key_did = ?", nil, keyDidOrDid).Scan(&reservedKey).Error; err == nil && reservedKey.KeyDid != "" { 76 + if err := s.db.Raw(ctx, "SELECT * FROM reserved_keys WHERE key_did = ?", nil, keyDidOrDid).Scan(&reservedKey).Error; err == nil && reservedKey.KeyDid != "" { 73 77 return &reservedKey, nil 74 78 } 75 79 76 - if err := s.db.Raw("SELECT * FROM reserved_keys WHERE did = ?", nil, keyDidOrDid).Scan(&reservedKey).Error; err == nil && reservedKey.KeyDid != "" { 80 + if err := s.db.Raw(ctx, "SELECT * FROM reserved_keys WHERE did = ?", nil, keyDidOrDid).Scan(&reservedKey).Error; err == nil && reservedKey.KeyDid != "" { 77 81 return &reservedKey, nil 78 82 } 79 83 80 84 return nil, nil 81 85 } 82 86 83 - func (s *Server) deleteReservedKey(keyDid string, did *string) error { 84 - if err := s.db.Exec("DELETE FROM reserved_keys WHERE key_did = ?", nil, keyDid).Error; err != nil { 87 + func (s *Server) deleteReservedKey(ctx context.Context, keyDid string, did *string) error { 88 + if err := s.db.Exec(ctx, "DELETE FROM reserved_keys WHERE key_did = ?", nil, keyDid).Error; err != nil { 85 89 return err 86 90 } 87 91 88 92 if did != nil && *did != "" { 89 - if err := s.db.Exec("DELETE FROM reserved_keys WHERE did = ?", nil, *did).Error; err != nil { 93 + if err := s.db.Exec(ctx, "DELETE FROM reserved_keys WHERE did = ?", nil, *did).Error; err != nil { 90 94 return err 91 95 } 92 96 }
+7 -4
server/handle_server_reset_password.go
··· 16 16 } 17 17 18 18 func (s *Server) handleServerResetPassword(e echo.Context) error { 19 + ctx := e.Request().Context() 20 + logger := s.logger.With("name", "handleServerResetPassword") 21 + 19 22 urepo := e.Get("repo").(*models.RepoActor) 20 23 21 24 var req ComAtprotoServerResetPasswordRequest 22 25 if err := e.Bind(&req); err != nil { 23 - s.logger.Error("error binding", "error", err) 26 + logger.Error("error binding", "error", err) 24 27 return helpers.ServerError(e, nil) 25 28 } 26 29 ··· 42 45 43 46 hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10) 44 47 if err != nil { 45 - s.logger.Error("error creating hash", "error", err) 48 + logger.Error("error creating hash", "error", err) 46 49 return helpers.ServerError(e, nil) 47 50 } 48 51 49 - if err := s.db.Exec("UPDATE repos SET password_reset_code = NULL, password_reset_code_expires_at = NULL, password = ? WHERE did = ?", nil, hash, urepo.Repo.Did).Error; err != nil { 50 - s.logger.Error("error updating repo", "error", err) 52 + if err := s.db.Exec(ctx, "UPDATE repos SET password_reset_code = NULL, password_reset_code_expires_at = NULL, password = ? WHERE did = ?", nil, hash, urepo.Repo.Did).Error; err != nil { 53 + logger.Error("error updating repo", "error", err) 51 54 return helpers.ServerError(e, nil) 52 55 } 53 56
+3 -1
server/handle_server_resolve_handle.go
··· 10 10 ) 11 11 12 12 func (s *Server) handleResolveHandle(e echo.Context) error { 13 + logger := s.logger.With("name", "handleServerResolveHandle") 14 + 13 15 type Resp struct { 14 16 Did string `json:"did"` 15 17 } ··· 28 30 ctx := context.WithValue(e.Request().Context(), "skip-cache", true) 29 31 did, err := s.passport.ResolveHandle(ctx, parsed.String()) 30 32 if err != nil { 31 - s.logger.Error("error resolving handle", "error", err) 33 + logger.Error("error resolving handle", "error", err) 32 34 return helpers.ServerError(e, nil) 33 35 } 34 36
+34 -9
server/handle_server_update_email.go
··· 11 11 type ComAtprotoServerUpdateEmailRequest struct { 12 12 Email string `json:"email" validate:"required"` 13 13 EmailAuthFactor bool `json:"emailAuthFactor"` 14 - Token string `json:"token" validate:"required"` 14 + Token string `json:"token"` 15 15 } 16 16 17 17 func (s *Server) handleServerUpdateEmail(e echo.Context) error { 18 + ctx := e.Request().Context() 19 + logger := s.logger.With("name", "handleServerUpdateEmail") 20 + 18 21 urepo := e.Get("repo").(*models.RepoActor) 19 22 20 23 var req ComAtprotoServerUpdateEmailRequest 21 24 if err := e.Bind(&req); err != nil { 22 - s.logger.Error("error binding", "error", err) 25 + logger.Error("error binding", "error", err) 23 26 return helpers.ServerError(e, nil) 24 27 } 25 28 ··· 27 30 return helpers.InputError(e, nil) 28 31 } 29 32 30 - if urepo.EmailUpdateCode == nil || urepo.EmailUpdateCodeExpiresAt == nil { 33 + // To disable email auth factor a token is required. 34 + // To enable email auth factor a token is not required. 35 + // If updating an email address, a token will be sent anyway 36 + if urepo.TwoFactorType != models.TwoFactorTypeNone && req.EmailAuthFactor == false && req.Token == "" { 31 37 return helpers.InvalidTokenError(e) 32 38 } 33 39 34 - if *urepo.EmailUpdateCode != req.Token { 35 - return helpers.InvalidTokenError(e) 40 + if req.Token != "" { 41 + if urepo.EmailUpdateCode == nil || urepo.EmailUpdateCodeExpiresAt == nil { 42 + return helpers.InvalidTokenError(e) 43 + } 44 + 45 + if *urepo.EmailUpdateCode != req.Token { 46 + return helpers.InvalidTokenError(e) 47 + } 48 + 49 + if time.Now().UTC().After(*urepo.EmailUpdateCodeExpiresAt) { 50 + return helpers.ExpiredTokenError(e) 51 + } 52 + } 53 + 54 + twoFactorType := models.TwoFactorTypeNone 55 + if req.EmailAuthFactor { 56 + twoFactorType = models.TwoFactorTypeEmail 36 57 } 37 58 38 - if time.Now().UTC().After(*urepo.EmailUpdateCodeExpiresAt) { 39 - return helpers.ExpiredTokenError(e) 59 + query := "UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, two_factor_type = ?, email = ?" 60 + 61 + if urepo.Email != req.Email { 62 + query += ",email_confirmed_at = NULL" 40 63 } 41 64 42 - if err := s.db.Exec("UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, email_confirmed_at = NULL, email = ? WHERE did = ?", nil, req.Email, urepo.Repo.Did).Error; err != nil { 43 - s.logger.Error("error updating repo", "error", err) 65 + query += " WHERE did = ?" 66 + 67 + if err := s.db.Exec(ctx, query, nil, twoFactorType, req.Email, urepo.Repo.Did).Error; err != nil { 68 + logger.Error("error updating repo", "error", err) 44 69 return helpers.ServerError(e, nil) 45 70 } 46 71
+14 -11
server/handle_sync_get_blob.go
··· 17 17 ) 18 18 19 19 func (s *Server) handleSyncGetBlob(e echo.Context) error { 20 + ctx := e.Request().Context() 21 + logger := s.logger.With("name", "handleSyncGetBlob") 22 + 20 23 did := e.QueryParam("did") 21 24 if did == "" { 22 25 return helpers.InputError(e, nil) ··· 32 35 return helpers.InputError(e, nil) 33 36 } 34 37 35 - urepo, err := s.getRepoActorByDid(did) 38 + urepo, err := s.getRepoActorByDid(ctx, did) 36 39 if err != nil { 37 - s.logger.Error("could not find user for requested blob", "error", err) 40 + logger.Error("could not find user for requested blob", "error", err) 38 41 return helpers.InputError(e, nil) 39 42 } 40 43 ··· 46 49 } 47 50 48 51 var blob models.Blob 49 - if err := s.db.Raw("SELECT * FROM blobs WHERE did = ? AND cid = ?", nil, did, c.Bytes()).Scan(&blob).Error; err != nil { 50 - s.logger.Error("error looking up blob", "error", err) 52 + if err := s.db.Raw(ctx, "SELECT * FROM blobs WHERE did = ? AND cid = ?", nil, did, c.Bytes()).Scan(&blob).Error; err != nil { 53 + logger.Error("error looking up blob", "error", err) 51 54 return helpers.ServerError(e, nil) 52 55 } 53 56 ··· 55 58 56 59 if blob.Storage == "sqlite" { 57 60 var parts []models.BlobPart 58 - if err := s.db.Raw("SELECT * FROM blob_parts WHERE blob_id = ? ORDER BY idx", nil, blob.ID).Scan(&parts).Error; err != nil { 59 - s.logger.Error("error getting blob parts", "error", err) 61 + if err := s.db.Raw(ctx, "SELECT * FROM blob_parts WHERE blob_id = ? ORDER BY idx", nil, blob.ID).Scan(&parts).Error; err != nil { 62 + logger.Error("error getting blob parts", "error", err) 60 63 return helpers.ServerError(e, nil) 61 64 } 62 65 ··· 66 69 } 67 70 } else if blob.Storage == "s3" { 68 71 if !(s.s3Config != nil && s.s3Config.BlobstoreEnabled) { 69 - s.logger.Error("s3 storage disabled") 72 + logger.Error("s3 storage disabled") 70 73 return helpers.ServerError(e, nil) 71 74 } 72 75 ··· 89 92 90 93 sess, err := session.NewSession(config) 91 94 if err != nil { 92 - s.logger.Error("error creating aws session", "error", err) 95 + logger.Error("error creating aws session", "error", err) 93 96 return helpers.ServerError(e, nil) 94 97 } 95 98 ··· 98 101 Bucket: aws.String(s.s3Config.Bucket), 99 102 Key: aws.String(blobKey), 100 103 }); err != nil { 101 - s.logger.Error("error getting blob from s3", "error", err) 104 + logger.Error("error getting blob from s3", "error", err) 102 105 return helpers.ServerError(e, nil) 103 106 } else { 104 107 read := 0 ··· 112 115 break 113 116 } 114 117 } else if err != nil && err != io.ErrUnexpectedEOF { 115 - s.logger.Error("error reading blob", "error", err) 118 + logger.Error("error reading blob", "error", err) 116 119 return helpers.ServerError(e, nil) 117 120 } 118 121 ··· 123 126 } 124 127 } 125 128 } else { 126 - s.logger.Error("unknown storage", "storage", blob.Storage) 129 + logger.Error("unknown storage", "storage", blob.Storage) 127 130 return helpers.ServerError(e, nil) 128 131 } 129 132
+3 -2
server/handle_sync_get_blocks.go
··· 18 18 19 19 func (s *Server) handleGetBlocks(e echo.Context) error { 20 20 ctx := e.Request().Context() 21 + logger := s.logger.With("name", "handleSyncGetBlocks") 21 22 22 23 var req ComAtprotoSyncGetBlocksRequest 23 24 if err := e.Bind(&req); err != nil { ··· 35 36 cids = append(cids, c) 36 37 } 37 38 38 - urepo, err := s.getRepoActorByDid(req.Did) 39 + urepo, err := s.getRepoActorByDid(ctx, req.Did) 39 40 if err != nil { 40 41 return helpers.ServerError(e, nil) 41 42 } ··· 52 53 }) 53 54 54 55 if _, err := carstore.LdWrite(buf, hb); err != nil { 55 - s.logger.Error("error writing to car", "error", err) 56 + logger.Error("error writing to car", "error", err) 56 57 return helpers.ServerError(e, nil) 57 58 } 58 59
+3 -1
server/handle_sync_get_latest_commit.go
··· 12 12 } 13 13 14 14 func (s *Server) handleSyncGetLatestCommit(e echo.Context) error { 15 + ctx := e.Request().Context() 16 + 15 17 did := e.QueryParam("did") 16 18 if did == "" { 17 19 return helpers.InputError(e, nil) 18 20 } 19 21 20 - urepo, err := s.getRepoActorByDid(did) 22 + urepo, err := s.getRepoActorByDid(ctx, did) 21 23 if err != nil { 22 24 return err 23 25 }
+8 -5
server/handle_sync_get_record.go
··· 13 13 ) 14 14 15 15 func (s *Server) handleSyncGetRecord(e echo.Context) error { 16 + ctx := e.Request().Context() 17 + logger := s.logger.With("name", "handleSyncGetRecord") 18 + 16 19 did := e.QueryParam("did") 17 20 collection := e.QueryParam("collection") 18 21 rkey := e.QueryParam("rkey") 19 22 20 23 var urepo models.Repo 21 - if err := s.db.Raw("SELECT * FROM repos WHERE did = ?", nil, did).Scan(&urepo).Error; err != nil { 22 - s.logger.Error("error getting repo", "error", err) 24 + if err := s.db.Raw(ctx, "SELECT * FROM repos WHERE did = ?", nil, did).Scan(&urepo).Error; err != nil { 25 + logger.Error("error getting repo", "error", err) 23 26 return helpers.ServerError(e, nil) 24 27 } 25 28 26 - root, blocks, err := s.repoman.getRecordProof(urepo, collection, rkey) 29 + root, blocks, err := s.repoman.getRecordProof(ctx, urepo, collection, rkey) 27 30 if err != nil { 28 31 return err 29 32 } ··· 36 39 }) 37 40 38 41 if _, err := carstore.LdWrite(buf, hb); err != nil { 39 - s.logger.Error("error writing to car", "error", err) 42 + logger.Error("error writing to car", "error", err) 40 43 return helpers.ServerError(e, nil) 41 44 } 42 45 43 46 for _, blk := range blocks { 44 47 if _, err := carstore.LdWrite(buf, blk.Cid().Bytes(), blk.RawData()); err != nil { 45 - s.logger.Error("error writing to car", "error", err) 48 + logger.Error("error writing to car", "error", err) 46 49 return helpers.ServerError(e, nil) 47 50 } 48 51 }
+6 -3
server/handle_sync_get_repo.go
··· 13 13 ) 14 14 15 15 func (s *Server) handleSyncGetRepo(e echo.Context) error { 16 + ctx := e.Request().Context() 17 + logger := s.logger.With("name", "handleSyncGetRepo") 18 + 16 19 did := e.QueryParam("did") 17 20 if did == "" { 18 21 return helpers.InputError(e, nil) 19 22 } 20 23 21 - urepo, err := s.getRepoActorByDid(did) 24 + urepo, err := s.getRepoActorByDid(ctx, did) 22 25 if err != nil { 23 26 return err 24 27 } ··· 36 39 buf := new(bytes.Buffer) 37 40 38 41 if _, err := carstore.LdWrite(buf, hb); err != nil { 39 - s.logger.Error("error writing to car", "error", err) 42 + logger.Error("error writing to car", "error", err) 40 43 return helpers.ServerError(e, nil) 41 44 } 42 45 43 46 var blocks []models.Block 44 - if err := s.db.Raw("SELECT * FROM blocks WHERE did = ? ORDER BY rev ASC", nil, urepo.Repo.Did).Scan(&blocks).Error; err != nil { 47 + if err := s.db.Raw(ctx, "SELECT * FROM blocks WHERE did = ? ORDER BY rev ASC", nil, urepo.Repo.Did).Scan(&blocks).Error; err != nil { 45 48 return err 46 49 } 47 50
+3 -1
server/handle_sync_get_repo_status.go
··· 14 14 15 15 // TODO: make this actually do the right thing 16 16 func (s *Server) handleSyncGetRepoStatus(e echo.Context) error { 17 + ctx := e.Request().Context() 18 + 17 19 did := e.QueryParam("did") 18 20 if did == "" { 19 21 return helpers.InputError(e, nil) 20 22 } 21 23 22 - urepo, err := s.getRepoActorByDid(did) 24 + urepo, err := s.getRepoActorByDid(ctx, did) 23 25 if err != nil { 24 26 return err 25 27 }
+8 -5
server/handle_sync_list_blobs.go
··· 14 14 } 15 15 16 16 func (s *Server) handleSyncListBlobs(e echo.Context) error { 17 + ctx := e.Request().Context() 18 + logger := s.logger.With("name", "handleSyncListBlobs") 19 + 17 20 did := e.QueryParam("did") 18 21 if did == "" { 19 22 return helpers.InputError(e, nil) ··· 35 38 } 36 39 params = append(params, limit) 37 40 38 - urepo, err := s.getRepoActorByDid(did) 41 + urepo, err := s.getRepoActorByDid(ctx, did) 39 42 if err != nil { 40 - s.logger.Error("could not find user for requested blobs", "error", err) 43 + logger.Error("could not find user for requested blobs", "error", err) 41 44 return helpers.InputError(e, nil) 42 45 } 43 46 ··· 49 52 } 50 53 51 54 var blobs []models.Blob 52 - if err := s.db.Raw("SELECT * FROM blobs WHERE did = ? "+cursorquery+" ORDER BY created_at DESC LIMIT ?", nil, params...).Scan(&blobs).Error; err != nil { 53 - s.logger.Error("error getting records", "error", err) 55 + if err := s.db.Raw(ctx, "SELECT * FROM blobs WHERE did = ? "+cursorquery+" ORDER BY created_at DESC LIMIT ?", nil, params...).Scan(&blobs).Error; err != nil { 56 + logger.Error("error getting records", "error", err) 54 57 return helpers.ServerError(e, nil) 55 58 } 56 59 ··· 58 61 for _, b := range blobs { 59 62 c, err := cid.Cast(b.Cid) 60 63 if err != nil { 61 - s.logger.Error("error casting cid", "error", err) 64 + logger.Error("error casting cid", "error", err) 62 65 return helpers.ServerError(e, nil) 63 66 } 64 67 cstrs = append(cstrs, c.String())
+82 -50
server/handle_sync_subscribe_repos.go
··· 7 7 "github.com/bluesky-social/indigo/events" 8 8 "github.com/bluesky-social/indigo/lex/util" 9 9 "github.com/btcsuite/websocket" 10 + "github.com/haileyok/cocoon/metrics" 10 11 "github.com/labstack/echo/v4" 11 12 ) 12 13 13 14 func (s *Server) handleSyncSubscribeRepos(e echo.Context) error { 14 - ctx := e.Request().Context() 15 + ctx, cancel := context.WithCancel(e.Request().Context()) 16 + defer cancel() 17 + 15 18 logger := s.logger.With("component", "subscribe-repos-websocket") 16 19 17 20 conn, err := websocket.Upgrade(e.Response().Writer, e.Request(), e.Response().Header(), 1<<10, 1<<10) ··· 24 27 logger = logger.With("ident", ident) 25 28 logger.Info("new connection established") 26 29 27 - evts, cancel, err := s.evtman.Subscribe(ctx, ident, func(evt *events.XRPCStreamEvent) bool { 30 + metrics.RelaysConnected.WithLabelValues(ident).Inc() 31 + defer func() { 32 + metrics.RelaysConnected.WithLabelValues(ident).Dec() 33 + }() 34 + 35 + evts, evtManCancel, err := s.evtman.Subscribe(ctx, ident, func(evt *events.XRPCStreamEvent) bool { 28 36 return true 29 37 }, nil) 30 38 if err != nil { 31 39 return err 32 40 } 33 - defer cancel() 41 + defer evtManCancel() 42 + 43 + // drop the connection whenever a subscriber disconnects from the socket, we should get errors 44 + go func() { 45 + for { 46 + select { 47 + case <-ctx.Done(): 48 + return 49 + default: 50 + if _, _, err := conn.ReadMessage(); err != nil { 51 + logger.Warn("websocket error", "err", err) 52 + cancel() 53 + return 54 + } 55 + } 56 + } 57 + }() 34 58 35 59 header := events.EventHeader{Op: events.EvtKindMessage} 36 60 for evt := range evts { 37 - wc, err := conn.NextWriter(websocket.BinaryMessage) 38 - if err != nil { 39 - logger.Error("error writing message to relay", "err", err) 40 - break 41 - } 61 + func() { 62 + defer func() { 63 + metrics.RelaySends.WithLabelValues(ident, header.MsgType).Inc() 64 + }() 42 65 43 - if ctx.Err() != nil { 44 - logger.Error("context error", "err", err) 45 - break 46 - } 66 + wc, err := conn.NextWriter(websocket.BinaryMessage) 67 + if err != nil { 68 + logger.Error("error writing message to relay", "err", err) 69 + return 70 + } 47 71 48 - var obj util.CBOR 49 - switch { 50 - case evt.Error != nil: 51 - header.Op = events.EvtKindErrorFrame 52 - obj = evt.Error 53 - case evt.RepoCommit != nil: 54 - header.MsgType = "#commit" 55 - obj = evt.RepoCommit 56 - case evt.RepoIdentity != nil: 57 - header.MsgType = "#identity" 58 - obj = evt.RepoIdentity 59 - case evt.RepoAccount != nil: 60 - header.MsgType = "#account" 61 - obj = evt.RepoAccount 62 - case evt.RepoInfo != nil: 63 - header.MsgType = "#info" 64 - obj = evt.RepoInfo 65 - default: 66 - logger.Warn("unrecognized event kind") 67 - return nil 68 - } 72 + if ctx.Err() != nil { 73 + logger.Error("context error", "err", err) 74 + return 75 + } 69 76 70 - if err := header.MarshalCBOR(wc); err != nil { 71 - logger.Error("failed to write header to relay", "err", err) 72 - break 73 - } 77 + var obj util.CBOR 78 + switch { 79 + case evt.Error != nil: 80 + header.Op = events.EvtKindErrorFrame 81 + obj = evt.Error 82 + case evt.RepoCommit != nil: 83 + header.MsgType = "#commit" 84 + obj = evt.RepoCommit 85 + case evt.RepoIdentity != nil: 86 + header.MsgType = "#identity" 87 + obj = evt.RepoIdentity 88 + case evt.RepoAccount != nil: 89 + header.MsgType = "#account" 90 + obj = evt.RepoAccount 91 + case evt.RepoInfo != nil: 92 + header.MsgType = "#info" 93 + obj = evt.RepoInfo 94 + default: 95 + logger.Warn("unrecognized event kind") 96 + return 97 + } 74 98 75 - if err := obj.MarshalCBOR(wc); err != nil { 76 - logger.Error("failed to write event to relay", "err", err) 77 - break 78 - } 99 + if err := header.MarshalCBOR(wc); err != nil { 100 + logger.Error("failed to write header to relay", "err", err) 101 + return 102 + } 103 + 104 + if err := obj.MarshalCBOR(wc); err != nil { 105 + logger.Error("failed to write event to relay", "err", err) 106 + return 107 + } 79 108 80 - if err := wc.Close(); err != nil { 81 - logger.Error("failed to flush-close our event write", "err", err) 82 - break 83 - } 109 + if err := wc.Close(); err != nil { 110 + logger.Error("failed to flush-close our event write", "err", err) 111 + return 112 + } 113 + }() 84 114 } 85 115 86 116 // we should tell the relay to request a new crawl at this point if we got disconnected 87 117 // use a new context since the old one might be cancelled at this point 88 - ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) 89 - defer cancel() 90 - if err := s.requestCrawl(ctx); err != nil { 91 - logger.Error("error requesting crawls", "err", err) 92 - } 118 + go func() { 119 + retryCtx, retryCancel := context.WithTimeout(context.Background(), 10*time.Second) 120 + defer retryCancel() 121 + if err := s.requestCrawl(retryCtx); err != nil { 122 + logger.Error("error requesting crawls", "err", err) 123 + } 124 + }() 93 125 94 126 return nil 95 127 }
+36
server/handle_well_known.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 + "strings" 5 6 6 7 "github.com/Azure/go-autorest/autorest/to" 8 + "github.com/haileyok/cocoon/internal/helpers" 7 9 "github.com/labstack/echo/v4" 10 + "gorm.io/gorm" 8 11 ) 9 12 10 13 var ( ··· 61 64 }, 62 65 }, 63 66 }) 67 + } 68 + 69 + func (s *Server) handleAtprotoDid(e echo.Context) error { 70 + ctx := e.Request().Context() 71 + logger := s.logger.With("name", "handleAtprotoDid") 72 + 73 + host := e.Request().Host 74 + if host == "" { 75 + return helpers.InputError(e, to.StringPtr("Invalid handle.")) 76 + } 77 + 78 + host = strings.Split(host, ":")[0] 79 + host = strings.ToLower(strings.TrimSpace(host)) 80 + 81 + if host == s.config.Hostname { 82 + return e.String(200, s.config.Did) 83 + } 84 + 85 + suffix := "." + s.config.Hostname 86 + if !strings.HasSuffix(host, suffix) { 87 + return e.NoContent(404) 88 + } 89 + 90 + actor, err := s.getActorByHandle(ctx, host) 91 + if err != nil { 92 + if err == gorm.ErrRecordNotFound { 93 + return e.NoContent(404) 94 + } 95 + logger.Error("error looking up actor by handle", "error", err) 96 + return helpers.ServerError(e, nil) 97 + } 98 + 99 + return e.String(200, actor.Did) 64 100 } 65 101 66 102 func (s *Server) handleOauthProtectedResource(e echo.Context) error {
+19
server/mail.go
··· 96 96 97 97 return nil 98 98 } 99 + 100 + func (s *Server) sendTwoFactorCode(email, handle, code string) error { 101 + if s.mail == nil { 102 + return nil 103 + } 104 + 105 + s.mailLk.Lock() 106 + defer s.mailLk.Unlock() 107 + 108 + s.mail.To(email) 109 + s.mail.Subject("2FA code for " + s.config.Hostname) 110 + s.mail.Plain().Set(fmt.Sprintf("Hello %s. Your 2FA code is %s. This code will expire in ten minutes.", handle, code)) 111 + 112 + if err := s.mail.Send(); err != nil { 113 + return err 114 + } 115 + 116 + return nil 117 + }
+42 -21
server/middleware.go
··· 37 37 38 38 func (s *Server) handleLegacySessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 39 39 return func(e echo.Context) error { 40 + ctx := e.Request().Context() 41 + logger := s.logger.With("name", "handleLegacySessionMiddleware") 42 + 40 43 authheader := e.Request().Header.Get("authorization") 41 44 if authheader == "" { 42 45 return e.JSON(401, map[string]string{"error": "Unauthorized"}) ··· 67 70 if hasLxm { 68 71 pts := strings.Split(e.Request().URL.String(), "/") 69 72 if lxm != pts[len(pts)-1] { 70 - s.logger.Error("service auth lxm incorrect", "lxm", lxm, "expected", pts[len(pts)-1], "error", err) 73 + logger.Error("service auth lxm incorrect", "lxm", lxm, "expected", pts[len(pts)-1], "error", err) 71 74 return helpers.InputError(e, nil) 72 75 } 73 76 74 77 maybeDid, ok := claims["iss"].(string) 75 78 if !ok { 76 - s.logger.Error("no iss in service auth token", "error", err) 79 + logger.Error("no iss in service auth token", "error", err) 77 80 return helpers.InputError(e, nil) 78 81 } 79 82 did = maybeDid 80 83 81 - maybeRepo, err := s.getRepoActorByDid(did) 84 + maybeRepo, err := s.getRepoActorByDid(ctx, did) 82 85 if err != nil { 83 - s.logger.Error("error fetching repo", "error", err) 86 + logger.Error("error fetching repo", "error", err) 84 87 return helpers.ServerError(e, nil) 85 88 } 86 89 repo = maybeRepo ··· 94 97 return s.privateKey.Public(), nil 95 98 }) 96 99 if err != nil { 97 - s.logger.Error("error parsing jwt", "error", err) 100 + logger.Error("error parsing jwt", "error", err) 98 101 return helpers.ExpiredTokenError(e) 99 102 } 100 103 ··· 107 110 hash := sha256.Sum256([]byte(signingInput)) 108 111 sigBytes, err := base64.RawURLEncoding.DecodeString(kpts[2]) 109 112 if err != nil { 110 - s.logger.Error("error decoding signature bytes", "error", err) 113 + logger.Error("error decoding signature bytes", "error", err) 111 114 return helpers.ServerError(e, nil) 112 115 } 113 116 114 117 if len(sigBytes) != 64 { 115 - s.logger.Error("incorrect sigbytes length", "length", len(sigBytes)) 118 + logger.Error("incorrect sigbytes length", "length", len(sigBytes)) 116 119 return helpers.ServerError(e, nil) 117 120 } 118 121 ··· 120 123 sBytes := sigBytes[32:] 121 124 rr, _ := secp256k1.NewScalarFromBytes((*[32]byte)(rBytes)) 122 125 ss, _ := secp256k1.NewScalarFromBytes((*[32]byte)(sBytes)) 126 + 127 + if repo == nil { 128 + sub, ok := claims["sub"].(string) 129 + if !ok { 130 + s.logger.Error("no sub claim in ES256K token and repo not set") 131 + return helpers.InvalidTokenError(e) 132 + } 133 + maybeRepo, err := s.getRepoActorByDid(ctx, sub) 134 + if err != nil { 135 + s.logger.Error("error fetching repo for ES256K verification", "error", err) 136 + return helpers.ServerError(e, nil) 137 + } 138 + repo = maybeRepo 139 + did = sub 140 + } 123 141 124 142 sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey) 125 143 if err != nil { 126 - s.logger.Error("can't load private key", "error", err) 144 + logger.Error("can't load private key", "error", err) 127 145 return err 128 146 } 129 147 130 148 pubKey, ok := sk.Public().(*secp256k1secec.PublicKey) 131 149 if !ok { 132 - s.logger.Error("error getting public key from sk") 150 + logger.Error("error getting public key from sk") 133 151 return helpers.ServerError(e, nil) 134 152 } 135 153 136 154 verified := pubKey.VerifyRaw(hash[:], rr, ss) 137 155 if !verified { 138 - s.logger.Error("error verifying", "error", err) 156 + logger.Error("error verifying", "error", err) 139 157 return helpers.ServerError(e, nil) 140 158 } 141 159 } ··· 159 177 Found bool 160 178 } 161 179 var result Result 162 - if err := s.db.Raw("SELECT EXISTS(SELECT 1 FROM "+table+" WHERE token = ?) AS found", nil, tokenstr).Scan(&result).Error; err != nil { 180 + if err := s.db.Raw(ctx, "SELECT EXISTS(SELECT 1 FROM "+table+" WHERE token = ?) AS found", nil, tokenstr).Scan(&result).Error; err != nil { 163 181 if err == gorm.ErrRecordNotFound { 164 182 return helpers.InvalidTokenError(e) 165 183 } 166 184 167 - s.logger.Error("error getting token from db", "error", err) 185 + logger.Error("error getting token from db", "error", err) 168 186 return helpers.ServerError(e, nil) 169 187 } 170 188 ··· 175 193 176 194 exp, ok := claims["exp"].(float64) 177 195 if !ok { 178 - s.logger.Error("error getting iat from token") 196 + logger.Error("error getting iat from token") 179 197 return helpers.ServerError(e, nil) 180 198 } 181 199 ··· 184 202 } 185 203 186 204 if repo == nil { 187 - maybeRepo, err := s.getRepoActorByDid(claims["sub"].(string)) 205 + maybeRepo, err := s.getRepoActorByDid(ctx, claims["sub"].(string)) 188 206 if err != nil { 189 - s.logger.Error("error fetching repo", "error", err) 207 + logger.Error("error fetching repo", "error", err) 190 208 return helpers.ServerError(e, nil) 191 209 } 192 210 repo = maybeRepo ··· 207 225 208 226 func (s *Server) handleOauthSessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 209 227 return func(e echo.Context) error { 228 + ctx := e.Request().Context() 229 + logger := s.logger.With("name", "handleOauthSessionMiddleware") 230 + 210 231 authheader := e.Request().Header.Get("authorization") 211 232 if authheader == "" { 212 233 return e.JSON(401, map[string]string{"error": "Unauthorized"}) ··· 238 259 "error": "use_dpop_nonce", 239 260 }) 240 261 } 241 - s.logger.Error("invalid dpop proof", "error", err) 262 + logger.Error("invalid dpop proof", "error", err) 242 263 return helpers.InputError(e, nil) 243 264 } 244 265 245 266 var oauthToken provider.OauthToken 246 - if err := s.db.Raw("SELECT * FROM oauth_tokens WHERE token = ?", nil, accessToken).Scan(&oauthToken).Error; err != nil { 247 - s.logger.Error("error finding access token in db", "error", err) 267 + if err := s.db.Raw(ctx, "SELECT * FROM oauth_tokens WHERE token = ?", nil, accessToken).Scan(&oauthToken).Error; err != nil { 268 + logger.Error("error finding access token in db", "error", err) 248 269 return helpers.InputError(e, nil) 249 270 } 250 271 ··· 253 274 } 254 275 255 276 if *oauthToken.Parameters.DpopJkt != proof.JKT { 256 - s.logger.Error("jkt mismatch", "token", oauthToken.Parameters.DpopJkt, "proof", proof.JKT) 277 + logger.Error("jkt mismatch", "token", oauthToken.Parameters.DpopJkt, "proof", proof.JKT) 257 278 return helpers.InputError(e, to.StringPtr("dpop jkt mismatch")) 258 279 } 259 280 ··· 266 287 }) 267 288 } 268 289 269 - repo, err := s.getRepoActorByDid(oauthToken.Sub) 290 + repo, err := s.getRepoActorByDid(ctx, oauthToken.Sub) 270 291 if err != nil { 271 - s.logger.Error("could not find actor in db", "error", err) 292 + logger.Error("could not find actor in db", "error", err) 272 293 return helpers.ServerError(e, nil) 273 294 } 274 295
+85 -32
server/repo.go
··· 17 17 lexutil "github.com/bluesky-social/indigo/lex/util" 18 18 "github.com/bluesky-social/indigo/repo" 19 19 "github.com/haileyok/cocoon/internal/db" 20 + "github.com/haileyok/cocoon/metrics" 20 21 "github.com/haileyok/cocoon/models" 21 22 "github.com/haileyok/cocoon/recording_blockstore" 22 23 blocks "github.com/ipfs/go-block-format" ··· 96 97 } 97 98 98 99 // TODO make use of swap commit 99 - func (rm *RepoMan) applyWrites(urepo models.Repo, writes []Op, swapCommit *string) ([]ApplyWriteResult, error) { 100 + func (rm *RepoMan) applyWrites(ctx context.Context, urepo models.Repo, writes []Op, swapCommit *string) ([]ApplyWriteResult, error) { 100 101 rootcid, err := cid.Cast(urepo.Root) 101 102 if err != nil { 102 103 return nil, err ··· 104 105 105 106 dbs := rm.s.getBlockstore(urepo.Did) 106 107 bs := recording_blockstore.New(dbs) 107 - r, err := repo.OpenRepo(context.TODO(), bs, rootcid) 108 + r, err := repo.OpenRepo(ctx, bs, rootcid) 108 109 109 - entries := []models.Record{} 110 110 var results []ApplyWriteResult 111 111 112 + entries := make([]models.Record, 0, len(writes)) 112 113 for i, op := range writes { 114 + // updates or deletes must supply an rkey 113 115 if op.Type != OpTypeCreate && op.Rkey == nil { 114 116 return nil, fmt.Errorf("invalid rkey") 115 117 } else if op.Type == OpTypeCreate && op.Rkey != nil { 116 - _, _, err := r.GetRecord(context.TODO(), op.Collection+"/"+*op.Rkey) 118 + // we should conver this op to an update if the rkey already exists 119 + _, _, err := r.GetRecord(ctx, fmt.Sprintf("%s/%s", op.Collection, *op.Rkey)) 117 120 if err == nil { 118 121 op.Type = OpTypeUpdate 119 122 } 120 123 } else if op.Rkey == nil { 124 + // creates that don't supply an rkey will have one generated for them 121 125 op.Rkey = to.StringPtr(rm.clock.Next().String()) 122 126 writes[i].Rkey = op.Rkey 123 127 } 124 128 129 + // validate the record key is actually valid 125 130 _, err := syntax.ParseRecordKey(*op.Rkey) 126 131 if err != nil { 127 132 return nil, err ··· 129 134 130 135 switch op.Type { 131 136 case OpTypeCreate: 132 - j, err := json.Marshal(*op.Record) 137 + // HACK: this fixes some type conversions, mainly around integers 138 + // first we convert to json bytes 139 + b, err := json.Marshal(*op.Record) 133 140 if err != nil { 134 141 return nil, err 135 142 } 136 - out, err := atdata.UnmarshalJSON(j) 143 + // then we use atdata.UnmarshalJSON to convert it back to a map 144 + out, err := atdata.UnmarshalJSON(b) 137 145 if err != nil { 138 146 return nil, err 139 147 } 148 + // finally we can cast to a MarshalableMap 140 149 mm := MarshalableMap(out) 141 150 142 151 // HACK: if a record doesn't contain a $type, we can manually set it here based on the op's collection 152 + // i forget why this is actually necessary? 143 153 if mm["$type"] == "" { 144 154 mm["$type"] = op.Collection 145 155 } 146 156 147 - nc, err := r.PutRecord(context.TODO(), op.Collection+"/"+*op.Rkey, &mm) 157 + nc, err := r.PutRecord(ctx, fmt.Sprintf("%s/%s", op.Collection, *op.Rkey), &mm) 148 158 if err != nil { 149 159 return nil, err 150 160 } 161 + 151 162 d, err := atdata.MarshalCBOR(mm) 152 163 if err != nil { 153 164 return nil, err 154 165 } 166 + 155 167 entries = append(entries, models.Record{ 156 168 Did: urepo.Did, 157 169 CreatedAt: rm.clock.Next().String(), ··· 160 172 Cid: nc.String(), 161 173 Value: d, 162 174 }) 175 + 163 176 results = append(results, ApplyWriteResult{ 164 177 Type: to.StringPtr(OpTypeCreate.String()), 165 178 Uri: to.StringPtr("at://" + urepo.Did + "/" + op.Collection + "/" + *op.Rkey), ··· 167 180 ValidationStatus: to.StringPtr("valid"), // TODO: obviously this might not be true atm lol 168 181 }) 169 182 case OpTypeDelete: 183 + // try to find the old record in the database 170 184 var old models.Record 171 - if err := rm.db.Raw("SELECT value FROM records WHERE did = ? AND nsid = ? AND rkey = ?", nil, urepo.Did, op.Collection, op.Rkey).Scan(&old).Error; err != nil { 185 + if err := rm.db.Raw(ctx, "SELECT value FROM records WHERE did = ? AND nsid = ? AND rkey = ?", nil, urepo.Did, op.Collection, op.Rkey).Scan(&old).Error; err != nil { 172 186 return nil, err 173 187 } 188 + 189 + // TODO: this is really confusing, and looking at it i have no idea why i did this. below when we are doing deletes, we 190 + // check if `cid` here is nil to indicate if we should delete. that really doesn't make much sense and its super illogical 191 + // when reading this code. i dont feel like fixing right now though so 174 192 entries = append(entries, models.Record{ 175 193 Did: urepo.Did, 176 194 Nsid: op.Collection, 177 195 Rkey: *op.Rkey, 178 196 Value: old.Value, 179 197 }) 180 - err := r.DeleteRecord(context.TODO(), op.Collection+"/"+*op.Rkey) 198 + 199 + // delete the record from the repo 200 + err := r.DeleteRecord(ctx, fmt.Sprintf("%s/%s", op.Collection, *op.Rkey)) 181 201 if err != nil { 182 202 return nil, err 183 203 } 204 + 205 + // add a result for the delete 184 206 results = append(results, ApplyWriteResult{ 185 207 Type: to.StringPtr(OpTypeDelete.String()), 186 208 }) 187 209 case OpTypeUpdate: 188 - j, err := json.Marshal(*op.Record) 210 + // HACK: same hack as above for type fixes 211 + b, err := json.Marshal(*op.Record) 189 212 if err != nil { 190 213 return nil, err 191 214 } 192 - out, err := atdata.UnmarshalJSON(j) 215 + out, err := atdata.UnmarshalJSON(b) 193 216 if err != nil { 194 217 return nil, err 195 218 } 196 219 mm := MarshalableMap(out) 197 - nc, err := r.UpdateRecord(context.TODO(), op.Collection+"/"+*op.Rkey, &mm) 220 + 221 + nc, err := r.UpdateRecord(ctx, fmt.Sprintf("%s/%s", op.Collection, *op.Rkey), &mm) 198 222 if err != nil { 199 223 return nil, err 200 224 } 225 + 201 226 d, err := atdata.MarshalCBOR(mm) 202 227 if err != nil { 203 228 return nil, err 204 229 } 230 + 205 231 entries = append(entries, models.Record{ 206 232 Did: urepo.Did, 207 233 CreatedAt: rm.clock.Next().String(), ··· 210 236 Cid: nc.String(), 211 237 Value: d, 212 238 }) 239 + 213 240 results = append(results, ApplyWriteResult{ 214 241 Type: to.StringPtr(OpTypeUpdate.String()), 215 242 Uri: to.StringPtr("at://" + urepo.Did + "/" + op.Collection + "/" + *op.Rkey), ··· 219 246 } 220 247 } 221 248 222 - newroot, rev, err := r.Commit(context.TODO(), urepo.SignFor) 249 + // commit and get the new root 250 + newroot, rev, err := r.Commit(ctx, urepo.SignFor) 223 251 if err != nil { 224 252 return nil, err 225 253 } 226 254 255 + for _, result := range results { 256 + if result.Type != nil { 257 + metrics.RepoOperations.WithLabelValues(*result.Type).Inc() 258 + } 259 + } 260 + 261 + // create a buffer for dumping our new cbor into 227 262 buf := new(bytes.Buffer) 228 263 264 + // first write the car header to the buffer 229 265 hb, err := cbor.DumpObject(&car.CarHeader{ 230 266 Roots: []cid.Cid{newroot}, 231 267 Version: 1, 232 268 }) 233 - 234 269 if _, err := carstore.LdWrite(buf, hb); err != nil { 235 270 return nil, err 236 271 } 237 272 238 - diffops, err := r.DiffSince(context.TODO(), rootcid) 273 + // get a diff of the changes to the repo 274 + diffops, err := r.DiffSince(ctx, rootcid) 239 275 if err != nil { 240 276 return nil, err 241 277 } 242 278 279 + // create the repo ops for the given diff 243 280 ops := make([]*atproto.SyncSubscribeRepos_RepoOp, 0, len(diffops)) 244 - 245 281 for _, op := range diffops { 246 282 var c cid.Cid 247 283 switch op.Op { ··· 270 306 }) 271 307 } 272 308 273 - blk, err := dbs.Get(context.TODO(), c) 309 + blk, err := dbs.Get(ctx, c) 274 310 if err != nil { 275 311 return nil, err 276 312 } 277 313 314 + // write the block to the buffer 278 315 if _, err := carstore.LdWrite(buf, blk.Cid().Bytes(), blk.RawData()); err != nil { 279 316 return nil, err 280 317 } 281 318 } 282 319 320 + // write the writelog to the buffer 283 321 for _, op := range bs.GetWriteLog() { 284 322 if _, err := carstore.LdWrite(buf, op.Cid().Bytes(), op.RawData()); err != nil { 285 323 return nil, err 286 324 } 287 325 } 288 326 327 + // blob blob blob blob blob :3 289 328 var blobs []lexutil.LexLink 290 329 for _, entry := range entries { 291 330 var cids []cid.Cid 331 + // whenever there is cid present, we know it's a create (dumb) 292 332 if entry.Cid != "" { 293 - if err := rm.s.db.Create(&entry, []clause.Expression{clause.OnConflict{ 333 + if err := rm.s.db.Create(ctx, &entry, []clause.Expression{clause.OnConflict{ 294 334 Columns: []clause.Column{{Name: "did"}, {Name: "nsid"}, {Name: "rkey"}}, 295 335 UpdateAll: true, 296 336 }}).Error; err != nil { 297 337 return nil, err 298 338 } 299 339 300 - cids, err = rm.incrementBlobRefs(urepo, entry.Value) 340 + // increment the given blob refs, yay 341 + cids, err = rm.incrementBlobRefs(ctx, urepo, entry.Value) 301 342 if err != nil { 302 343 return nil, err 303 344 } 304 345 } else { 305 - if err := rm.s.db.Delete(&entry, nil).Error; err != nil { 346 + // as i noted above this is dumb. but we delete whenever the cid is nil. it works solely becaue the pkey 347 + // is did + collection + rkey. i still really want to separate that out, or use a different type to make 348 + // this less confusing/easy to read. alas, its 2 am and yea no 349 + if err := rm.s.db.Delete(ctx, &entry, nil).Error; err != nil { 306 350 return nil, err 307 351 } 308 - cids, err = rm.decrementBlobRefs(urepo, entry.Value) 352 + 353 + // TODO: 354 + cids, err = rm.decrementBlobRefs(ctx, urepo, entry.Value) 309 355 if err != nil { 310 356 return nil, err 311 357 } 312 358 } 313 359 360 + // add all the relevant blobs to the blobs list of blobs. blob ^.^ 314 361 for _, c := range cids { 315 362 blobs = append(blobs, lexutil.LexLink(c)) 316 363 } 317 364 } 318 365 319 - rm.s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 366 + // NOTE: using the request ctx seems a bit suss here, so using a background context. i'm not sure if this 367 + // runs sync or not 368 + rm.s.evtman.AddEvent(context.Background(), &events.XRPCStreamEvent{ 320 369 RepoCommit: &atproto.SyncSubscribeRepos_Commit{ 321 370 Repo: urepo.Did, 322 371 Blocks: buf.Bytes(), ··· 330 379 }, 331 380 }) 332 381 333 - if err := rm.s.UpdateRepo(context.TODO(), urepo.Did, newroot, rev); err != nil { 382 + if err := rm.s.UpdateRepo(ctx, urepo.Did, newroot, rev); err != nil { 334 383 return nil, err 335 384 } 336 385 ··· 345 394 return results, nil 346 395 } 347 396 348 - func (rm *RepoMan) getRecordProof(urepo models.Repo, collection, rkey string) (cid.Cid, []blocks.Block, error) { 397 + // this is a fun little guy. to get a proof, we need to read the record out of the blockstore and record how we actually 398 + // got to the guy. we'll wrap a new blockstore in a recording blockstore, then return the log for proof 399 + func (rm *RepoMan) getRecordProof(ctx context.Context, urepo models.Repo, collection, rkey string) (cid.Cid, []blocks.Block, error) { 349 400 c, err := cid.Cast(urepo.Root) 350 401 if err != nil { 351 402 return cid.Undef, nil, err ··· 354 405 dbs := rm.s.getBlockstore(urepo.Did) 355 406 bs := recording_blockstore.New(dbs) 356 407 357 - r, err := repo.OpenRepo(context.TODO(), bs, c) 408 + r, err := repo.OpenRepo(ctx, bs, c) 358 409 if err != nil { 359 410 return cid.Undef, nil, err 360 411 } 361 412 362 - _, _, err = r.GetRecordBytes(context.TODO(), collection+"/"+rkey) 413 + _, _, err = r.GetRecordBytes(ctx, fmt.Sprintf("%s/%s", collection, rkey)) 363 414 if err != nil { 364 415 return cid.Undef, nil, err 365 416 } ··· 367 418 return c, bs.GetReadLog(), nil 368 419 } 369 420 370 - func (rm *RepoMan) incrementBlobRefs(urepo models.Repo, cbor []byte) ([]cid.Cid, error) { 421 + func (rm *RepoMan) incrementBlobRefs(ctx context.Context, urepo models.Repo, cbor []byte) ([]cid.Cid, error) { 371 422 cids, err := getBlobCidsFromCbor(cbor) 372 423 if err != nil { 373 424 return nil, err 374 425 } 375 426 376 427 for _, c := range cids { 377 - if err := rm.db.Exec("UPDATE blobs SET ref_count = ref_count + 1 WHERE did = ? AND cid = ?", nil, urepo.Did, c.Bytes()).Error; err != nil { 428 + if err := rm.db.Exec(ctx, "UPDATE blobs SET ref_count = ref_count + 1 WHERE did = ? AND cid = ?", nil, urepo.Did, c.Bytes()).Error; err != nil { 378 429 return nil, err 379 430 } 380 431 } ··· 382 433 return cids, nil 383 434 } 384 435 385 - func (rm *RepoMan) decrementBlobRefs(urepo models.Repo, cbor []byte) ([]cid.Cid, error) { 436 + func (rm *RepoMan) decrementBlobRefs(ctx context.Context, urepo models.Repo, cbor []byte) ([]cid.Cid, error) { 386 437 cids, err := getBlobCidsFromCbor(cbor) 387 438 if err != nil { 388 439 return nil, err ··· 393 444 ID uint 394 445 Count int 395 446 } 396 - if err := rm.db.Raw("UPDATE blobs SET ref_count = ref_count - 1 WHERE did = ? AND cid = ? RETURNING id, ref_count", nil, urepo.Did, c.Bytes()).Scan(&res).Error; err != nil { 447 + if err := rm.db.Raw(ctx, "UPDATE blobs SET ref_count = ref_count - 1 WHERE did = ? AND cid = ? RETURNING id, ref_count", nil, urepo.Did, c.Bytes()).Scan(&res).Error; err != nil { 397 448 return nil, err 398 449 } 399 450 451 + // TODO: this does _not_ handle deletions of blobs that are on s3 storage!!!! we need to get the blob, see what 452 + // storage it is in, and clean up s3!!!! 400 453 if res.Count == 0 { 401 - if err := rm.db.Exec("DELETE FROM blobs WHERE id = ?", nil, res.ID).Error; err != nil { 454 + if err := rm.db.Exec(ctx, "DELETE FROM blobs WHERE id = ?", nil, res.ID).Error; err != nil { 402 455 return nil, err 403 456 } 404 - if err := rm.db.Exec("DELETE FROM blob_parts WHERE blob_id = ?", nil, res.ID).Error; err != nil { 457 + if err := rm.db.Exec(ctx, "DELETE FROM blob_parts WHERE blob_id = ?", nil, res.ID).Error; err != nil { 405 458 return nil, err 406 459 } 407 460 }
+81 -73
server/server.go
··· 39 39 "github.com/haileyok/cocoon/oauth/provider" 40 40 "github.com/haileyok/cocoon/plc" 41 41 "github.com/ipfs/go-cid" 42 + "github.com/labstack/echo-contrib/echoprometheus" 42 43 echo_session "github.com/labstack/echo-contrib/session" 43 44 "github.com/labstack/echo/v4" 44 45 "github.com/labstack/echo/v4/middleware" ··· 89 90 } 90 91 91 92 type Args struct { 93 + Logger *slog.Logger 94 + 92 95 Addr string 93 96 DbName string 94 97 DbType string 95 98 DatabaseURL string 96 - Logger *slog.Logger 97 99 Version string 98 100 Did string 99 101 Hostname string ··· 102 104 ContactEmail string 103 105 Relays []string 104 106 AdminPassword string 107 + RequireInvite bool 105 108 106 109 SmtpUser string 107 110 SmtpPass string ··· 126 129 EnforcePeering bool 127 130 Relays []string 128 131 AdminPassword string 132 + RequireInvite bool 129 133 SmtpEmail string 130 134 SmtpName string 131 135 BlockstoreVariant BlockstoreVariant ··· 207 211 } 208 212 209 213 func New(args *Args) (*Server, error) { 214 + if args.Logger == nil { 215 + args.Logger = slog.Default() 216 + } 217 + 218 + logger := args.Logger.With("name", "New") 219 + 210 220 if args.Addr == "" { 211 221 return nil, fmt.Errorf("addr must be set") 212 222 } ··· 235 245 return nil, fmt.Errorf("admin password must be set") 236 246 } 237 247 238 - if args.Logger == nil { 239 - args.Logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{})) 240 - } 241 - 242 248 if args.SessionSecret == "" { 243 249 panic("SESSION SECRET WAS NOT SET. THIS IS REQUIRED. ") 244 250 } ··· 246 252 e := echo.New() 247 253 248 254 e.Pre(middleware.RemoveTrailingSlash()) 249 - e.Pre(slogecho.New(args.Logger)) 255 + e.Pre(slogecho.New(args.Logger.With("component", "slogecho"))) 250 256 e.Use(echo_session.Middleware(sessions.NewCookieStore([]byte(args.SessionSecret)))) 257 + e.Use(echoprometheus.NewMiddleware("cocoon")) 251 258 e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 252 259 AllowOrigins: []string{"*"}, 253 260 AllowHeaders: []string{"*"}, ··· 309 316 if err != nil { 310 317 return nil, fmt.Errorf("failed to connect to postgres: %w", err) 311 318 } 312 - args.Logger.Info("connected to PostgreSQL database") 319 + logger.Info("connected to PostgreSQL database") 313 320 default: 314 321 gdb, err = gorm.Open(sqlite.Open(args.DbName), &gorm.Config{}) 315 322 if err != nil { 316 323 return nil, fmt.Errorf("failed to open sqlite database: %w", err) 317 324 } 318 - args.Logger.Info("connected to SQLite database", "path", args.DbName) 325 + gdb.Exec("PRAGMA journal_mode=WAL") 326 + gdb.Exec("PRAGMA synchronous=NORMAL") 327 + 328 + logger.Info("connected to SQLite database", "path", args.DbName) 319 329 } 320 330 dbw := db.NewDB(gdb) 321 331 ··· 358 368 var nonceSecret []byte 359 369 maybeSecret, err := os.ReadFile("nonce.secret") 360 370 if err != nil && !os.IsNotExist(err) { 361 - args.Logger.Error("error attempting to read nonce secret", "error", err) 371 + logger.Error("error attempting to read nonce secret", "error", err) 362 372 } else { 363 373 nonceSecret = maybeSecret 364 374 } ··· 379 389 EnforcePeering: false, 380 390 Relays: args.Relays, 381 391 AdminPassword: args.AdminPassword, 392 + RequireInvite: args.RequireInvite, 382 393 SmtpName: args.SmtpName, 383 394 SmtpEmail: args.SmtpEmail, 384 395 BlockstoreVariant: args.BlockstoreVariant, ··· 395 406 Hostname: args.Hostname, 396 407 ClientManagerArgs: client.ManagerArgs{ 397 408 Cli: oauthCli, 398 - Logger: args.Logger, 409 + Logger: args.Logger.With("component", "oauth-client-manager"), 399 410 }, 400 411 DpopManagerArgs: dpop.ManagerArgs{ 401 412 NonceSecret: nonceSecret, 402 413 NonceRotationInterval: constants.NonceMaxRotationInterval / 3, 403 414 OnNonceSecretCreated: func(newNonce []byte) { 404 415 if err := os.WriteFile("nonce.secret", newNonce, 0644); err != nil { 405 - args.Logger.Error("error writing new nonce secret", "error", err) 416 + logger.Error("error writing new nonce secret", "error", err) 406 417 } 407 418 }, 408 - Logger: args.Logger, 419 + Logger: args.Logger.With("component", "dpop-manager"), 409 420 Hostname: args.Hostname, 410 421 }, 411 422 }), ··· 442 453 s.echo.GET("/", s.handleRoot) 443 454 s.echo.GET("/xrpc/_health", s.handleHealth) 444 455 s.echo.GET("/.well-known/did.json", s.handleWellKnown) 456 + s.echo.GET("/.well-known/atproto-did", s.handleAtprotoDid) 445 457 s.echo.GET("/.well-known/oauth-protected-resource", s.handleOauthProtectedResource) 446 458 s.echo.GET("/.well-known/oauth-authorization-server", s.handleOauthAuthorizationServer) 447 459 s.echo.GET("/robots.txt", s.handleRobots) ··· 465 477 s.echo.GET("/xrpc/com.atproto.sync.subscribeRepos", s.handleSyncSubscribeRepos) 466 478 s.echo.GET("/xrpc/com.atproto.sync.listBlobs", s.handleSyncListBlobs) 467 479 s.echo.GET("/xrpc/com.atproto.sync.getBlob", s.handleSyncGetBlob) 480 + 481 + // labels 482 + s.echo.GET("/xrpc/com.atproto.label.queryLabels", s.handleLabelQueryLabels) 468 483 469 484 // account 470 485 s.echo.GET("/account", s.handleAccount) ··· 528 543 } 529 544 530 545 func (s *Server) Serve(ctx context.Context) error { 546 + logger := s.logger.With("name", "Serve") 547 + 531 548 s.addRoutes() 532 549 533 - s.logger.Info("migrating...") 550 + logger.Info("migrating...") 534 551 535 552 s.db.AutoMigrate( 536 553 &models.Actor{}, ··· 547 564 &provider.OauthAuthorizationRequest{}, 548 565 ) 549 566 550 - s.logger.Info("starting cocoon") 567 + logger.Info("starting cocoon") 551 568 552 569 go func() { 553 570 if err := s.httpd.ListenAndServe(); err != nil { ··· 559 576 560 577 go func() { 561 578 if err := s.requestCrawl(ctx); err != nil { 562 - s.logger.Error("error requesting crawls", "err", err) 579 + logger.Error("error requesting crawls", "err", err) 563 580 } 564 581 }() 565 582 ··· 577 594 578 595 logger.Info("requesting crawl with configured relays") 579 596 580 - if time.Now().Sub(s.lastRequestCrawl) <= 1*time.Minute { 597 + if time.Since(s.lastRequestCrawl) <= 1*time.Minute { 581 598 return fmt.Errorf("a crawl request has already been made within the last minute") 582 599 } 583 600 ··· 600 617 } 601 618 602 619 func (s *Server) doBackup() { 620 + logger := s.logger.With("name", "doBackup") 621 + 603 622 if s.dbType == "postgres" { 604 - s.logger.Info("skipping S3 backup - PostgreSQL backups should be handled externally (pg_dump, managed database backups, etc.)") 623 + logger.Info("skipping S3 backup - PostgreSQL backups should be handled externally (pg_dump, managed database backups, etc.)") 605 624 return 606 625 } 607 626 608 627 start := time.Now() 609 628 610 - s.logger.Info("beginning backup to s3...") 611 - 612 - var buf bytes.Buffer 613 - if err := func() error { 614 - s.logger.Info("reading database bytes...") 615 - s.db.Lock() 616 - defer s.db.Unlock() 629 + logger.Info("beginning backup to s3...") 617 630 618 - sf, err := os.Open(s.dbName) 619 - if err != nil { 620 - return fmt.Errorf("error opening database for backup: %w", err) 621 - } 622 - defer sf.Close() 631 + tmpFile := fmt.Sprintf("/tmp/cocoon-backup-%s.db", time.Now().Format(time.RFC3339Nano)) 632 + defer os.Remove(tmpFile) 623 633 624 - if _, err := io.Copy(&buf, sf); err != nil { 625 - return fmt.Errorf("error reading bytes of backup db: %w", err) 626 - } 627 - 628 - return nil 629 - }(); err != nil { 630 - s.logger.Error("error backing up database", "error", err) 634 + if err := s.db.Client().Exec(fmt.Sprintf("VACUUM INTO '%s'", tmpFile)).Error; err != nil { 635 + logger.Error("error creating tmp backup file", "err", err) 631 636 return 632 637 } 633 638 634 - if err := func() error { 635 - s.logger.Info("sending to s3...") 639 + backupData, err := os.ReadFile(tmpFile) 640 + if err != nil { 641 + logger.Error("error reading tmp backup file", "err", err) 642 + return 643 + } 636 644 637 - currTime := time.Now().Format("2006-01-02_15-04-05") 638 - key := "cocoon-backup-" + currTime + ".db" 645 + logger.Info("sending to s3...") 639 646 640 - config := &aws.Config{ 641 - Region: aws.String(s.s3Config.Region), 642 - Credentials: credentials.NewStaticCredentials(s.s3Config.AccessKey, s.s3Config.SecretKey, ""), 643 - } 647 + currTime := time.Now().Format("2006-01-02_15-04-05") 648 + key := "cocoon-backup-" + currTime + ".db" 644 649 645 - if s.s3Config.Endpoint != "" { 646 - config.Endpoint = aws.String(s.s3Config.Endpoint) 647 - config.S3ForcePathStyle = aws.Bool(true) 648 - } 650 + config := &aws.Config{ 651 + Region: aws.String(s.s3Config.Region), 652 + Credentials: credentials.NewStaticCredentials(s.s3Config.AccessKey, s.s3Config.SecretKey, ""), 653 + } 649 654 650 - sess, err := session.NewSession(config) 651 - if err != nil { 652 - return err 653 - } 655 + if s.s3Config.Endpoint != "" { 656 + config.Endpoint = aws.String(s.s3Config.Endpoint) 657 + config.S3ForcePathStyle = aws.Bool(true) 658 + } 654 659 655 - svc := s3.New(sess) 660 + sess, err := session.NewSession(config) 661 + if err != nil { 662 + logger.Error("error creating s3 session", "err", err) 663 + return 664 + } 656 665 657 - if _, err := svc.PutObject(&s3.PutObjectInput{ 658 - Bucket: aws.String(s.s3Config.Bucket), 659 - Key: aws.String(key), 660 - Body: bytes.NewReader(buf.Bytes()), 661 - }); err != nil { 662 - return fmt.Errorf("error uploading file to s3: %w", err) 663 - } 666 + svc := s3.New(sess) 664 667 665 - s.logger.Info("finished uploading backup to s3", "key", key, "duration", time.Now().Sub(start).Seconds()) 666 - 667 - return nil 668 - }(); err != nil { 669 - s.logger.Error("error uploading database backup", "error", err) 668 + if _, err := svc.PutObject(&s3.PutObjectInput{ 669 + Bucket: aws.String(s.s3Config.Bucket), 670 + Key: aws.String(key), 671 + Body: bytes.NewReader(backupData), 672 + }); err != nil { 673 + logger.Error("error uploading file to s3", "err", err) 670 674 return 671 675 } 672 676 673 - os.WriteFile("last-backup.txt", []byte(time.Now().String()), 0644) 677 + logger.Info("finished uploading backup to s3", "key", key, "duration", time.Since(start).Seconds()) 678 + 679 + os.WriteFile("last-backup.txt", []byte(time.Now().Format(time.RFC3339Nano)), 0644) 674 680 } 675 681 676 682 func (s *Server) backupRoutine() { 683 + logger := s.logger.With("name", "backupRoutine") 684 + 677 685 if s.s3Config == nil || !s.s3Config.BackupsEnabled { 678 686 return 679 687 } 680 688 681 689 if s.s3Config.Region == "" { 682 - s.logger.Warn("no s3 region configured but backups are enabled. backups will not run.") 690 + logger.Warn("no s3 region configured but backups are enabled. backups will not run.") 683 691 return 684 692 } 685 693 686 694 if s.s3Config.Bucket == "" { 687 - s.logger.Warn("no s3 bucket configured but backups are enabled. backups will not run.") 695 + logger.Warn("no s3 bucket configured but backups are enabled. backups will not run.") 688 696 return 689 697 } 690 698 691 699 if s.s3Config.AccessKey == "" { 692 - s.logger.Warn("no s3 access key configured but backups are enabled. backups will not run.") 700 + logger.Warn("no s3 access key configured but backups are enabled. backups will not run.") 693 701 return 694 702 } 695 703 696 704 if s.s3Config.SecretKey == "" { 697 - s.logger.Warn("no s3 secret key configured but backups are enabled. backups will not run.") 705 + logger.Warn("no s3 secret key configured but backups are enabled. backups will not run.") 698 706 return 699 707 } 700 708 ··· 703 711 if err != nil { 704 712 shouldBackupNow = true 705 713 } else { 706 - lastBackup, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", string(lastBackupStr)) 714 + lastBackup, err := time.Parse(time.RFC3339Nano, string(lastBackupStr)) 707 715 if err != nil { 708 716 shouldBackupNow = true 709 - } else if time.Now().Sub(lastBackup).Seconds() > 3600 { 717 + } else if time.Since(lastBackup).Seconds() > 3600 { 710 718 shouldBackupNow = true 711 719 } 712 720 } ··· 722 730 } 723 731 724 732 func (s *Server) UpdateRepo(ctx context.Context, did string, root cid.Cid, rev string) error { 725 - if err := s.db.Exec("UPDATE repos SET root = ?, rev = ? WHERE did = ?", nil, root.Bytes(), rev, did).Error; err != nil { 733 + if err := s.db.Exec(ctx, "UPDATE repos SET root = ?, rev = ? WHERE did = ?", nil, root.Bytes(), rev, did).Error; err != nil { 726 734 return err 727 735 } 728 736
+4 -3
server/session.go
··· 1 1 package server 2 2 3 3 import ( 4 + "context" 4 5 "time" 5 6 6 7 "github.com/golang-jwt/jwt/v4" ··· 13 14 RefreshToken string 14 15 } 15 16 16 - func (s *Server) createSession(repo *models.Repo) (*Session, error) { 17 + func (s *Server) createSession(ctx context.Context, repo *models.Repo) (*Session, error) { 17 18 now := time.Now() 18 19 accexp := now.Add(3 * time.Hour) 19 20 refexp := now.Add(7 * 24 * time.Hour) ··· 49 50 return nil, err 50 51 } 51 52 52 - if err := s.db.Create(&models.Token{ 53 + if err := s.db.Create(ctx, &models.Token{ 53 54 Token: accessString, 54 55 Did: repo.Did, 55 56 RefreshToken: refreshString, ··· 59 60 return nil, err 60 61 } 61 62 62 - if err := s.db.Create(&models.RefreshToken{ 63 + if err := s.db.Create(ctx, &models.RefreshToken{ 63 64 Token: refreshString, 64 65 Did: repo.Did, 65 66 CreatedAt: now,
+4
server/templates/signin.html
··· 26 26 type="password" 27 27 placeholder="Password" 28 28 /> 29 + {{ if .flashes.tokenrequired }} 30 + <br /> 31 + <input name="token" id="token" placeholder="Enter your 2FA token" /> 32 + {{ end }} 29 33 <input name="query_params" type="hidden" value="{{ .QueryParams }}" /> 30 34 <button class="primary" type="submit" value="Login">Login</button> 31 35 </form>
+3 -3
sqlite_blockstore/sqlite_blockstore.go
··· 45 45 return maybeBlock, nil 46 46 } 47 47 48 - if err := bs.db.Raw("SELECT * FROM blocks WHERE did = ? AND cid = ?", nil, bs.did, cid.Bytes()).Scan(&block).Error; err != nil { 48 + if err := bs.db.Raw(ctx, "SELECT * FROM blocks WHERE did = ? AND cid = ?", nil, bs.did, cid.Bytes()).Scan(&block).Error; err != nil { 49 49 return nil, err 50 50 } 51 51 ··· 71 71 Value: block.RawData(), 72 72 } 73 73 74 - if err := bs.db.Create(&b, []clause.Expression{clause.OnConflict{ 74 + if err := bs.db.Create(ctx, &b, []clause.Expression{clause.OnConflict{ 75 75 Columns: []clause.Column{{Name: "did"}, {Name: "cid"}}, 76 76 UpdateAll: true, 77 77 }}).Error; err != nil { ··· 94 94 } 95 95 96 96 func (bs *SqliteBlockstore) PutMany(ctx context.Context, blocks []blocks.Block) error { 97 - tx := bs.db.BeginDangerously() 97 + tx := bs.db.Begin(ctx) 98 98 99 99 for _, block := range blocks { 100 100 bs.inserts[block.Cid()] = block
+1 -1
test.go
··· 32 32 33 33 u.Path = "xrpc/com.atproto.sync.subscribeRepos" 34 34 conn, _, err := dialer.Dial(u.String(), http.Header{ 35 - "User-Agent": []string{fmt.Sprintf("hot-topic/0.0.0")}, 35 + "User-Agent": []string{"cocoon-test/0.0.0"}, 36 36 }) 37 37 if err != nil { 38 38 return fmt.Errorf("subscribing to firehose failed (dialing): %w", err)