forked from hailey.at/cocoon
An atproto PDS written in Go

Compare changes

Choose any two refs to compare.

Changed files
+976 -528
cmd
cocoon
internal
db
metrics
models
oauth
client
dpop
server
templates
sqlite_blockstore
+7
cmd/cocoon/main.go
··· 9 "os" 10 "time" 11 12 "github.com/bluesky-social/indigo/atproto/atcrypto" 13 "github.com/bluesky-social/indigo/atproto/syntax" 14 "github.com/haileyok/cocoon/internal/helpers" ··· 154 Name: "fallback-proxy", 155 EnvVars: []string{"COCOON_FALLBACK_PROXY"}, 156 }, 157 }, 158 Commands: []*cli.Command{ 159 runServe, ··· 177 Flags: []cli.Flag{}, 178 Action: func(cmd *cli.Context) error { 179 180 s, err := server.New(&server.Args{ 181 Addr: cmd.String("addr"), 182 DbName: cmd.String("db-name"), 183 DbType: cmd.String("db-type"),
··· 9 "os" 10 "time" 11 12 + "github.com/bluesky-social/go-util/pkg/telemetry" 13 "github.com/bluesky-social/indigo/atproto/atcrypto" 14 "github.com/bluesky-social/indigo/atproto/syntax" 15 "github.com/haileyok/cocoon/internal/helpers" ··· 155 Name: "fallback-proxy", 156 EnvVars: []string{"COCOON_FALLBACK_PROXY"}, 157 }, 158 + telemetry.CLIFlagDebug, 159 + telemetry.CLIFlagMetricsListenAddress, 160 }, 161 Commands: []*cli.Command{ 162 runServe, ··· 180 Flags: []cli.Flag{}, 181 Action: func(cmd *cli.Context) error { 182 183 + logger := telemetry.StartLogger(cmd) 184 + telemetry.StartMetrics(cmd) 185 + 186 s, err := server.New(&server.Args{ 187 + Logger: logger, 188 Addr: cmd.String("addr"), 189 DbName: cmd.String("db-name"), 190 DbType: cmd.String("db-type"),
+15 -13
go.mod
··· 1 module github.com/haileyok/cocoon 2 3 - go 1.24.1 4 5 require ( 6 github.com/Azure/go-autorest/autorest/to v0.4.1 7 github.com/aws/aws-sdk-go v1.55.7 8 github.com/bluesky-social/indigo v0.0.0-20251009212240-20524de167fe 9 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 10 github.com/domodwyer/mailyak/v3 v3.6.2 11 github.com/go-pkgz/expirable-cache/v3 v3.0.0 12 github.com/go-playground/validator v9.31.0+incompatible 13 github.com/golang-jwt/jwt/v4 v4.5.2 14 - github.com/google/uuid v1.4.0 15 github.com/gorilla/sessions v1.4.0 16 github.com/gorilla/websocket v1.5.1 17 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b ··· 26 github.com/labstack/echo/v4 v4.13.3 27 github.com/lestrrat-go/jwx/v2 v2.0.12 28 github.com/multiformats/go-multihash v0.2.3 29 github.com/samber/slog-echo v1.16.1 30 github.com/urfave/cli/v2 v2.27.6 31 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e 32 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b 33 - golang.org/x/crypto v0.38.0 34 gorm.io/driver/sqlite v1.5.7 35 gorm.io/gorm v1.25.12 36 ) ··· 56 github.com/gorilla/securecookie v1.1.2 // indirect 57 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 58 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 59 - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect 60 github.com/hashicorp/golang-lru v1.0.2 // indirect 61 github.com/ipfs/bbloom v0.0.4 // indirect 62 github.com/ipfs/go-blockservice v0.5.2 // indirect ··· 102 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 103 github.com/opentracing/opentracing-go v1.2.0 // indirect 104 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 105 - github.com/prometheus/client_golang v1.22.0 // indirect 106 github.com/prometheus/client_model v0.6.2 // indirect 107 - github.com/prometheus/common v0.63.0 // indirect 108 github.com/prometheus/procfs v0.16.1 // indirect 109 github.com/russross/blackfriday/v2 v2.1.0 // indirect 110 github.com/samber/lo v1.49.1 // indirect ··· 114 github.com/valyala/fasttemplate v1.2.2 // indirect 115 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 116 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 117 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect 118 go.opentelemetry.io/otel v1.29.0 // indirect 119 go.opentelemetry.io/otel/metric v1.29.0 // indirect 120 go.opentelemetry.io/otel/trace v1.29.0 // indirect 121 go.uber.org/atomic v1.11.0 // indirect 122 go.uber.org/multierr v1.11.0 // indirect 123 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 128 golang.org/x/time v0.11.0 // indirect 129 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 130 - google.golang.org/protobuf v1.36.6 // indirect 131 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect 132 gopkg.in/inf.v0 v0.9.1 // indirect 133 - gorm.io/driver/postgres v1.5.7 // indirect 134 lukechampine.com/blake3 v1.2.1 // indirect 135 )
··· 1 module github.com/haileyok/cocoon 2 3 + go 1.24.5 4 5 require ( 6 github.com/Azure/go-autorest/autorest/to v0.4.1 7 github.com/aws/aws-sdk-go v1.55.7 8 + github.com/bluesky-social/go-util v0.0.0-20251012040650-2ebbf57f5934 9 github.com/bluesky-social/indigo v0.0.0-20251009212240-20524de167fe 10 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 11 github.com/domodwyer/mailyak/v3 v3.6.2 12 github.com/go-pkgz/expirable-cache/v3 v3.0.0 13 github.com/go-playground/validator v9.31.0+incompatible 14 github.com/golang-jwt/jwt/v4 v4.5.2 15 + github.com/google/uuid v1.6.0 16 github.com/gorilla/sessions v1.4.0 17 github.com/gorilla/websocket v1.5.1 18 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b ··· 27 github.com/labstack/echo/v4 v4.13.3 28 github.com/lestrrat-go/jwx/v2 v2.0.12 29 github.com/multiformats/go-multihash v0.2.3 30 + github.com/prometheus/client_golang v1.23.2 31 github.com/samber/slog-echo v1.16.1 32 github.com/urfave/cli/v2 v2.27.6 33 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e 34 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b 35 + golang.org/x/crypto v0.41.0 36 + gorm.io/driver/postgres v1.5.7 37 gorm.io/driver/sqlite v1.5.7 38 gorm.io/gorm v1.25.12 39 ) ··· 59 github.com/gorilla/securecookie v1.1.2 // indirect 60 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 61 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 62 + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 63 github.com/hashicorp/golang-lru v1.0.2 // indirect 64 github.com/ipfs/bbloom v0.0.4 // indirect 65 github.com/ipfs/go-blockservice v0.5.2 // indirect ··· 105 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 106 github.com/opentracing/opentracing-go v1.2.0 // indirect 107 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 108 github.com/prometheus/client_model v0.6.2 // indirect 109 + github.com/prometheus/common v0.66.1 // indirect 110 github.com/prometheus/procfs v0.16.1 // indirect 111 github.com/russross/blackfriday/v2 v2.1.0 // indirect 112 github.com/samber/lo v1.49.1 // indirect ··· 116 github.com/valyala/fasttemplate v1.2.2 // indirect 117 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 118 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 119 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect 120 go.opentelemetry.io/otel v1.29.0 // indirect 121 go.opentelemetry.io/otel/metric v1.29.0 // indirect 122 go.opentelemetry.io/otel/trace v1.29.0 // indirect 123 go.uber.org/atomic v1.11.0 // indirect 124 go.uber.org/multierr v1.11.0 // indirect 125 go.uber.org/zap v1.26.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 131 golang.org/x/time v0.11.0 // indirect 132 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 133 + google.golang.org/protobuf v1.36.9 // indirect 134 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect 135 gopkg.in/inf.v0 v0.9.1 // indirect 136 lukechampine.com/blake3 v1.2.1 // indirect 137 )
+42 -33
go.sum
··· 16 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 17 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= 18 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= 19 github.com/bluesky-social/indigo v0.0.0-20251009212240-20524de167fe h1:VBhaqE5ewQgXbY5SfSWFZC/AwHFo7cHxZKFYi2ce9Yo= 20 github.com/bluesky-social/indigo v0.0.0-20251009212240-20524de167fe/go.mod h1:RuQVrCGm42QNsgumKaR6se+XkFKfCPNwdCiTvqKRUck= 21 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= ··· 39 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 40 github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= 41 github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= 42 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 43 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 44 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= ··· 77 github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 78 github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 79 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= 82 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 83 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 84 github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= ··· 95 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= 96 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 97 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= 102 github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 103 github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 104 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= ··· 195 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 196 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 197 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 198 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 199 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 200 github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= ··· 206 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 207 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 208 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 209 github.com/labstack/echo-contrib v0.17.4 h1:g5mfsrJfJTKv+F5uNKCyrjLK7js+ZW6HTjg4FnDxxgk= 210 github.com/labstack/echo-contrib v0.17.4/go.mod h1:9O7ZPAHUeMGTOAfg80YqQduHzt0CzLak36PZRldYrZ0= 211 github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= ··· 289 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 290 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 291 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= 294 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 295 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= 298 github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 299 github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 300 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= ··· 319 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 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 324 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 325 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= ··· 327 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= 332 github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 333 github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= 334 github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= ··· 354 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 355 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 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= 359 go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= 360 go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= 361 go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= ··· 367 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 368 go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 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= 372 go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 373 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 374 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= ··· 378 go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 379 go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 380 go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 381 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 382 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 383 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 384 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= 389 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= 390 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= 391 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= ··· 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= 400 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 401 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 402 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= ··· 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= 412 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 413 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 414 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 415 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= 420 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 421 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 422 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ··· 432 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= 437 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= ··· 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= 450 golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 451 golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 452 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= ··· 461 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= 466 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 467 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 468 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 469 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 470 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 471 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= 474 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 475 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 476 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
··· 16 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 17 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= 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= 21 github.com/bluesky-social/indigo v0.0.0-20251009212240-20524de167fe h1:VBhaqE5ewQgXbY5SfSWFZC/AwHFo7cHxZKFYi2ce9Yo= 22 github.com/bluesky-social/indigo v0.0.0-20251009212240-20524de167fe/go.mod h1:RuQVrCGm42QNsgumKaR6se+XkFKfCPNwdCiTvqKRUck= 23 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= ··· 41 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 42 github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= 43 github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= 44 + github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 45 + github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 46 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 47 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 48 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= ··· 81 github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 82 github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 83 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 84 + github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 85 + github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 86 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 87 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 88 github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= ··· 99 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= 100 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 101 github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 102 + github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= 103 + github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 104 + github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= 105 + github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 106 github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 107 github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 108 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= ··· 199 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 200 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 201 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 202 + github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 203 + github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 204 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 205 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 206 github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= ··· 212 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 213 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 214 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 215 + github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 216 + github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 217 github.com/labstack/echo-contrib v0.17.4 h1:g5mfsrJfJTKv+F5uNKCyrjLK7js+ZW6HTjg4FnDxxgk= 218 github.com/labstack/echo-contrib v0.17.4/go.mod h1:9O7ZPAHUeMGTOAfg80YqQduHzt0CzLak36PZRldYrZ0= 219 github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= ··· 297 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 298 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 299 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 300 + github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= 301 + github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= 302 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 303 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 304 + github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= 305 + github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= 306 github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 307 github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 308 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= ··· 327 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 328 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 329 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 330 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 331 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 332 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= ··· 334 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 335 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 336 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 337 + github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 338 + github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 339 github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 340 github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= 341 github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= ··· 361 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 362 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 363 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 364 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= 365 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= 366 go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= 367 go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= 368 go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= ··· 374 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 375 go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 376 go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 377 + go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 378 + go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 379 go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 380 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 381 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= ··· 385 go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 386 go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 387 go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 388 + go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= 389 + go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= 390 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 391 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 392 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 393 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 394 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 395 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 396 + golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= 397 + golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 398 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= 399 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= 400 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= ··· 404 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 405 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 406 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 407 + golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= 408 + golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= 409 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 410 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 411 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= ··· 416 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 417 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 418 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 419 + golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= 420 + golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 421 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 422 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 423 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 424 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 425 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 426 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 427 + golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 428 + golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 429 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 430 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 431 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ··· 441 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 442 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 443 golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 444 + golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 445 + golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 446 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 447 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 448 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= ··· 454 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 455 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 456 golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 457 + golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= 458 + golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= 459 golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 460 golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 461 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= ··· 470 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 471 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 472 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 473 + golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= 474 + golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= 475 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 476 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 477 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 478 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 479 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 480 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 481 + google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= 482 + google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 483 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 484 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 485 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+15 -14
internal/db/db.go
··· 1 package db 2 3 import ( 4 "sync" 5 6 "gorm.io/gorm" ··· 19 } 20 } 21 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) 26 } 27 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) 32 } 33 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...) 38 } 39 40 - func (db *DB) Raw(sql string, clauses []clause.Expression, values ...any) *gorm.DB { 41 - return db.cli.Clauses(clauses...).Raw(sql, values...) 42 } 43 44 func (db *DB) AutoMigrate(models ...any) error { 45 return db.cli.AutoMigrate(models...) 46 } 47 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) 52 } 53 54 - func (db *DB) First(dest any, conds ...any) *gorm.DB { 55 - return db.cli.First(dest, conds...) 56 } 57 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() 63 } 64 65 func (db *DB) Lock() {
··· 1 package db 2 3 import ( 4 + "context" 5 "sync" 6 7 "gorm.io/gorm" ··· 20 } 21 } 22 23 + func (db *DB) Create(ctx context.Context, value any, clauses []clause.Expression) *gorm.DB { 24 db.mu.Lock() 25 defer db.mu.Unlock() 26 + return db.cli.WithContext(ctx).Clauses(clauses...).Create(value) 27 } 28 29 + func (db *DB) Save(ctx context.Context, value any, clauses []clause.Expression) *gorm.DB { 30 db.mu.Lock() 31 defer db.mu.Unlock() 32 + return db.cli.WithContext(ctx).Clauses(clauses...).Save(value) 33 } 34 35 + func (db *DB) Exec(ctx context.Context, sql string, clauses []clause.Expression, values ...any) *gorm.DB { 36 db.mu.Lock() 37 defer db.mu.Unlock() 38 + return db.cli.WithContext(ctx).Clauses(clauses...).Exec(sql, values...) 39 } 40 41 + func (db *DB) Raw(ctx context.Context, sql string, clauses []clause.Expression, values ...any) *gorm.DB { 42 + return db.cli.WithContext(ctx).Clauses(clauses...).Raw(sql, values...) 43 } 44 45 func (db *DB) AutoMigrate(models ...any) error { 46 return db.cli.AutoMigrate(models...) 47 } 48 49 + func (db *DB) Delete(ctx context.Context, value any, clauses []clause.Expression) *gorm.DB { 50 db.mu.Lock() 51 defer db.mu.Unlock() 52 + return db.cli.WithContext(ctx).Clauses(clauses...).Delete(value) 53 } 54 55 + func (db *DB) First(ctx context.Context, dest any, conds ...any) *gorm.DB { 56 + return db.cli.WithContext(ctx).First(dest, conds...) 57 } 58 59 // 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 60 // 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. 61 // e.g. when we do apply writes we should also be using a transcation but we don't right now 62 + func (db *DB) BeginDangerously(ctx context.Context) *gorm.DB { 63 + return db.cli.WithContext(ctx).Begin() 64 } 65 66 func (db *DB) Lock() {
+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 "github.com/bluesky-social/indigo/atproto/atcrypto" 9 ) 10 11 type Repo struct { 12 Did string `gorm:"primaryKey"` 13 CreatedAt time.Time ··· 29 Root []byte 30 Preferences []byte 31 Deactivated bool 32 } 33 34 func (r *Repo) SignFor(ctx context.Context, did string, msg []byte) ([]byte, error) { ··· 121 } 122 123 type ReservedKey struct { 124 - KeyDid string `gorm:"primaryKey"` 125 - Did *string `gorm:"index"` 126 PrivateKey []byte 127 CreatedAt time.Time `gorm:"index"` 128 }
··· 8 "github.com/bluesky-social/indigo/atproto/atcrypto" 9 ) 10 11 + type TwoFactorType string 12 + 13 + var ( 14 + TwoFactorTypeNone = TwoFactorType("none") 15 + TwoFactorTypeEmail = TwoFactorType("email") 16 + ) 17 + 18 type Repo struct { 19 Did string `gorm:"primaryKey"` 20 CreatedAt time.Time ··· 36 Root []byte 37 Preferences []byte 38 Deactivated bool 39 + TwoFactorCode *string 40 + TwoFactorCodeExpiresAt *time.Time 41 + TwoFactorType TwoFactorType `gorm:"default:none"` 42 } 43 44 func (r *Repo) SignFor(ctx context.Context, did string, msg []byte) ([]byte, error) { ··· 131 } 132 133 type ReservedKey struct { 134 + KeyDid string `gorm:"primaryKey"` 135 + Did *string `gorm:"index"` 136 PrivateKey []byte 137 CreatedAt time.Time `gorm:"index"` 138 }
-1
oauth/client/manager.go
··· 72 } 73 74 jwks = k 75 - } else if metadata.JWKS != nil { 76 } else if metadata.JWKSURI != nil { 77 maybeJwks, err := cm.getClientJwks(ctx, clientId, *metadata.JWKSURI) 78 if err != nil {
··· 72 } 73 74 jwks = k 75 } else if metadata.JWKSURI != nil { 76 maybeJwks, err := cm.getClientJwks(ctx, clientId, *metadata.JWKSURI) 77 if err != nil {
+1 -1
oauth/dpop/jti_cache.go
··· 14 } 15 16 func newJTICache(size int) *jtiCache { 17 - cache := cache.NewCache[string, bool]().WithTTL(24 * time.Hour).WithLRU().WithTTL(constants.JTITtl) 18 return &jtiCache{ 19 cache: cache, 20 mu: sync.Mutex{},
··· 14 } 15 16 func newJTICache(size int) *jtiCache { 17 + cache := cache.NewCache[string, bool]().WithTTL(24 * time.Hour).WithLRU().WithTTL(constants.JTITtl).WithMaxKeys(size) 18 return &jtiCache{ 19 cache: cache, 20 mu: sync.Mutex{},
+10 -8
server/common.go
··· 1 package server 2 3 import ( 4 "github.com/haileyok/cocoon/models" 5 ) 6 7 - func (s *Server) getActorByHandle(handle string) (*models.Actor, error) { 8 var actor models.Actor 9 - if err := s.db.First(&actor, models.Actor{Handle: handle}).Error; err != nil { 10 return nil, err 11 } 12 return &actor, nil 13 } 14 15 - func (s *Server) getRepoByEmail(email string) (*models.Repo, error) { 16 var repo models.Repo 17 - if err := s.db.First(&repo, models.Repo{Email: email}).Error; err != nil { 18 return nil, err 19 } 20 return &repo, nil 21 } 22 23 - func (s *Server) getRepoActorByEmail(email string) (*models.RepoActor, error) { 24 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 { 26 return nil, err 27 } 28 return &repo, nil 29 } 30 31 - func (s *Server) getRepoActorByDid(did string) (*models.RepoActor, error) { 32 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 { 34 return nil, err 35 } 36 return &repo, nil
··· 1 package server 2 3 import ( 4 + "context" 5 + 6 "github.com/haileyok/cocoon/models" 7 ) 8 9 + func (s *Server) getActorByHandle(ctx context.Context, handle string) (*models.Actor, error) { 10 var actor models.Actor 11 + if err := s.db.First(ctx, &actor, models.Actor{Handle: handle}).Error; err != nil { 12 return nil, err 13 } 14 return &actor, nil 15 } 16 17 + func (s *Server) getRepoByEmail(ctx context.Context, email string) (*models.Repo, error) { 18 var repo models.Repo 19 + if err := s.db.First(ctx, &repo, models.Repo{Email: email}).Error; err != nil { 20 return nil, err 21 } 22 return &repo, nil 23 } 24 25 + func (s *Server) getRepoActorByEmail(ctx context.Context, email string) (*models.RepoActor, error) { 26 var repo models.RepoActor 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 { 28 return nil, err 29 } 30 return &repo, nil 31 } 32 33 + func (s *Server) getRepoActorByDid(ctx context.Context, did string) (*models.RepoActor, error) { 34 var repo models.RepoActor 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 { 36 return nil, err 37 } 38 return &repo, nil
+4 -2
server/handle_account.go
··· 12 13 func (s *Server) handleAccount(e echo.Context) error { 14 ctx := e.Request().Context() 15 repo, sess, err := s.getSessionRepoOrErr(e) 16 if err != nil { 17 return e.Redirect(303, "/account/signin") ··· 20 oldestPossibleSession := time.Now().Add(constants.ConfidentialClientSessionLifetime) 21 22 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 sess.AddFlash("Unable to fetch sessions. See server logs for more details.", "error") 26 sess.Save(e.Request(), e.Response()) 27 return e.Render(200, "account.html", map[string]any{
··· 12 13 func (s *Server) handleAccount(e echo.Context) error { 14 ctx := e.Request().Context() 15 + logger := s.logger.With("name", "handleAuth") 16 + 17 repo, sess, err := s.getSessionRepoOrErr(e) 18 if err != nil { 19 return e.Redirect(303, "/account/signin") ··· 22 oldestPossibleSession := time.Now().Add(constants.ConfidentialClientSessionLifetime) 23 24 var tokens []provider.OauthToken 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) 27 sess.AddFlash("Unable to fetch sessions. See server logs for more details.", "error") 28 sess.Save(e.Request(), e.Response()) 29 return e.Render(200, "account.html", map[string]any{
+8 -5
server/handle_account_revoke.go
··· 5 "github.com/labstack/echo/v4" 6 ) 7 8 - type AccountRevokeRequest struct { 9 Token string `form:"token"` 10 } 11 12 func (s *Server) handleAccountRevoke(e echo.Context) error { 13 - var req AccountRevokeRequest 14 if err := e.Bind(&req); err != nil { 15 - s.logger.Error("could not bind account revoke request", "error", err) 16 return helpers.ServerError(e, nil) 17 } 18 ··· 21 return e.Redirect(303, "/account/signin") 22 } 23 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) 26 sess.AddFlash("Unable to revoke session. See server logs for more details.", "error") 27 sess.Save(e.Request(), e.Response()) 28 return e.Redirect(303, "/account")
··· 5 "github.com/labstack/echo/v4" 6 ) 7 8 + type AccountRevokeInput struct { 9 Token string `form:"token"` 10 } 11 12 func (s *Server) handleAccountRevoke(e echo.Context) error { 13 + ctx := e.Request().Context() 14 + logger := s.logger.With("name", "handleAcocuntRevoke") 15 + 16 + var req AccountRevokeInput 17 if err := e.Bind(&req); err != nil { 18 + logger.Error("could not bind account revoke request", "error", err) 19 return helpers.ServerError(e, nil) 20 } 21 ··· 24 return e.Redirect(303, "/account/signin") 25 } 26 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) 29 sess.AddFlash("Unable to revoke session. See server logs for more details.", "error") 30 sess.Save(e.Request(), e.Response()) 31 return e.Redirect(303, "/account")
+68 -16
server/handle_account_signin.go
··· 2 3 import ( 4 "errors" 5 "strings" 6 7 "github.com/bluesky-social/indigo/atproto/syntax" 8 "github.com/gorilla/sessions" ··· 14 "gorm.io/gorm" 15 ) 16 17 - type OauthSigninRequest struct { 18 - Username string `form:"username"` 19 - Password string `form:"password"` 20 - QueryParams string `form:"query_params"` 21 } 22 23 func (s *Server) getSessionRepoOrErr(e echo.Context) (*models.RepoActor, *sessions.Session, error) { 24 sess, err := session.Get("session", e) 25 if err != nil { 26 return nil, nil, err ··· 31 return nil, sess, errors.New("did was not set in session") 32 } 33 34 - repo, err := s.getRepoActorByDid(did) 35 if err != nil { 36 return nil, sess, err 37 } ··· 42 func getFlashesFromSession(e echo.Context, sess *sessions.Session) map[string]any { 43 defer sess.Save(e.Request(), e.Response()) 44 return map[string]any{ 45 - "errors": sess.Flashes("error"), 46 - "successes": sess.Flashes("success"), 47 } 48 } 49 ··· 60 } 61 62 func (s *Server) handleAccountSigninPost(e echo.Context) error { 63 - var req OauthSigninRequest 64 if err := e.Bind(&req); err != nil { 65 - s.logger.Error("error binding sign in req", "error", err) 66 return helpers.ServerError(e, nil) 67 } 68 ··· 76 idtype = "handle" 77 } else { 78 idtype = "email" 79 } 80 81 // TODO: we should make this a helper since we do it for the base create_session as well ··· 83 var err error 84 switch idtype { 85 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 87 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 89 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 91 } 92 if err != nil { 93 if err == gorm.ErrRecordNotFound { ··· 96 sess.AddFlash("Something went wrong!", "error") 97 } 98 sess.Save(e.Request(), e.Response()) 99 - return e.Redirect(303, "/account/signin") 100 } 101 102 if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil { ··· 106 sess.AddFlash("Something went wrong!", "error") 107 } 108 sess.Save(e.Request(), e.Response()) 109 - return e.Redirect(303, "/account/signin") 110 } 111 112 sess.Options = &sessions.Options{ ··· 122 return err 123 } 124 125 - if req.QueryParams != "" { 126 - return e.Redirect(303, "/oauth/authorize?"+req.QueryParams) 127 } else { 128 return e.Redirect(303, "/account") 129 }
··· 2 3 import ( 4 "errors" 5 + "fmt" 6 "strings" 7 + "time" 8 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 "github.com/gorilla/sessions" ··· 16 "gorm.io/gorm" 17 ) 18 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"` 24 } 25 26 func (s *Server) getSessionRepoOrErr(e echo.Context) (*models.RepoActor, *sessions.Session, error) { 27 + ctx := e.Request().Context() 28 + 29 sess, err := session.Get("session", e) 30 if err != nil { 31 return nil, nil, err ··· 36 return nil, sess, errors.New("did was not set in session") 37 } 38 39 + repo, err := s.getRepoActorByDid(ctx, did) 40 if err != nil { 41 return nil, sess, err 42 } ··· 47 func getFlashesFromSession(e echo.Context, sess *sessions.Session) map[string]any { 48 defer sess.Save(e.Request(), e.Response()) 49 return map[string]any{ 50 + "errors": sess.Flashes("error"), 51 + "successes": sess.Flashes("success"), 52 + "tokenrequired": sess.Flashes("tokenrequired"), 53 } 54 } 55 ··· 66 } 67 68 func (s *Server) handleAccountSigninPost(e echo.Context) error { 69 + ctx := e.Request().Context() 70 + logger := s.logger.With("name", "handleAccountSigninPost") 71 + 72 + var req OauthSigninInput 73 if err := e.Bind(&req); err != nil { 74 + logger.Error("error binding sign in req", "error", err) 75 return helpers.ServerError(e, nil) 76 } 77 ··· 85 idtype = "handle" 86 } else { 87 idtype = "email" 88 + } 89 + 90 + queryParams := "" 91 + if req.QueryParams != "" { 92 + queryParams = fmt.Sprintf("?%s", req.QueryParams) 93 } 94 95 // TODO: we should make this a helper since we do it for the base create_session as well ··· 97 var err error 98 switch idtype { 99 case "did": 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 101 case "handle": 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 103 case "email": 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 105 } 106 if err != nil { 107 if err == gorm.ErrRecordNotFound { ··· 110 sess.AddFlash("Something went wrong!", "error") 111 } 112 sess.Save(e.Request(), e.Response()) 113 + return e.Redirect(303, "/account/signin"+queryParams) 114 } 115 116 if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil { ··· 120 sess.AddFlash("Something went wrong!", "error") 121 } 122 sess.Save(e.Request(), e.Response()) 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 + } 162 } 163 164 sess.Options = &sessions.Options{ ··· 174 return err 175 } 176 177 + if queryParams != "" { 178 + return e.Redirect(303, "/oauth/authorize"+queryParams) 179 } else { 180 return e.Redirect(303, "/account") 181 }
+3 -1
server/handle_actor_put_preferences.go
··· 10 // This is kinda lame. Not great to implement app.bsky in the pds, but alas 11 12 func (s *Server) handleActorPutPreferences(e echo.Context) error { 13 repo := e.Get("repo").(*models.RepoActor) 14 15 var prefs map[string]any ··· 22 return err 23 } 24 25 - if err := s.db.Exec("UPDATE repos SET preferences = ? WHERE did = ?", nil, b, repo.Repo.Did).Error; err != nil { 26 return err 27 } 28
··· 10 // This is kinda lame. Not great to implement app.bsky in the pds, but alas 11 12 func (s *Server) handleActorPutPreferences(e echo.Context) error { 13 + ctx := e.Request().Context() 14 + 15 repo := e.Get("repo").(*models.RepoActor) 16 17 var prefs map[string]any ··· 24 return err 25 } 26 27 + if err := s.db.Exec(ctx, "UPDATE repos SET preferences = ? WHERE did = ?", nil, b, repo.Repo.Did).Error; err != nil { 28 return err 29 } 30
+6 -3
server/handle_identity_request_plc_operation.go
··· 10 ) 11 12 func (s *Server) handleIdentityRequestPlcOperationSignature(e echo.Context) error { 13 urepo := e.Get("repo").(*models.RepoActor) 14 15 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 16 eat := time.Now().Add(10 * time.Minute).UTC() 17 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) 20 return helpers.ServerError(e, nil) 21 } 22 23 if err := s.sendPlcTokenReset(urepo.Email, urepo.Handle, code); err != nil { 24 - s.logger.Error("error sending mail", "error", err) 25 return helpers.ServerError(e, nil) 26 } 27
··· 10 ) 11 12 func (s *Server) handleIdentityRequestPlcOperationSignature(e echo.Context) error { 13 + ctx := e.Request().Context() 14 + logger := s.logger.With("name", "handleIdentityRequestPlcOperationSignature") 15 + 16 urepo := e.Get("repo").(*models.RepoActor) 17 18 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 19 eat := time.Now().Add(10 * time.Minute).UTC() 20 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) 23 return helpers.ServerError(e, nil) 24 } 25 26 if err := s.sendPlcTokenReset(urepo.Email, urepo.Handle, code); err != nil { 27 + logger.Error("error sending mail", "error", err) 28 return helpers.ServerError(e, nil) 29 } 30
+8 -6
server/handle_identity_sign_plc_operation.go
··· 27 } 28 29 func (s *Server) handleSignPlcOperation(e echo.Context) error { 30 repo := e.Get("repo").(*models.RepoActor) 31 32 var req ComAtprotoSignPlcOperationRequest 33 if err := e.Bind(&req); err != nil { 34 - s.logger.Error("error binding", "error", err) 35 return helpers.ServerError(e, nil) 36 } 37 ··· 54 ctx := context.WithValue(e.Request().Context(), "skip-cache", true) 55 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did) 56 if err != nil { 57 - s.logger.Error("error fetching doc", "error", err) 58 return helpers.ServerError(e, nil) 59 } 60 ··· 83 84 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey) 85 if err != nil { 86 - s.logger.Error("error parsing signing key", "error", err) 87 return helpers.ServerError(e, nil) 88 } 89 90 if err := s.plcClient.SignOp(k, &op); err != nil { 91 - s.logger.Error("error signing plc operation", "error", err) 92 return helpers.ServerError(e, nil) 93 } 94 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 return helpers.ServerError(e, nil) 98 } 99
··· 27 } 28 29 func (s *Server) handleSignPlcOperation(e echo.Context) error { 30 + logger := s.logger.With("name", "handleSignPlcOperation") 31 + 32 repo := e.Get("repo").(*models.RepoActor) 33 34 var req ComAtprotoSignPlcOperationRequest 35 if err := e.Bind(&req); err != nil { 36 + logger.Error("error binding", "error", err) 37 return helpers.ServerError(e, nil) 38 } 39 ··· 56 ctx := context.WithValue(e.Request().Context(), "skip-cache", true) 57 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did) 58 if err != nil { 59 + logger.Error("error fetching doc", "error", err) 60 return helpers.ServerError(e, nil) 61 } 62 ··· 85 86 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey) 87 if err != nil { 88 + logger.Error("error parsing signing key", "error", err) 89 return helpers.ServerError(e, nil) 90 } 91 92 if err := s.plcClient.SignOp(k, &op); err != nil { 93 + logger.Error("error signing plc operation", "error", err) 94 return helpers.ServerError(e, nil) 95 } 96 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) 99 return helpers.ServerError(e, nil) 100 } 101
+6 -4
server/handle_identity_submit_plc_operation.go
··· 21 } 22 23 func (s *Server) handleSubmitPlcOperation(e echo.Context) error { 24 repo := e.Get("repo").(*models.RepoActor) 25 26 var req ComAtprotoSubmitPlcOperationRequest 27 if err := e.Bind(&req); err != nil { 28 - s.logger.Error("error binding", "error", err) 29 return helpers.ServerError(e, nil) 30 } 31 ··· 40 41 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey) 42 if err != nil { 43 - s.logger.Error("error parsing key", "error", err) 44 return helpers.ServerError(e, nil) 45 } 46 required, err := s.plcClient.CreateDidCredentials(k, "", repo.Actor.Handle) 47 if err != nil { 48 - s.logger.Error("error crating did credentials", "error", err) 49 return helpers.ServerError(e, nil) 50 } 51 ··· 72 } 73 74 if err := s.passport.BustDoc(context.TODO(), repo.Repo.Did); err != nil { 75 - s.logger.Warn("error busting did doc", "error", err) 76 } 77 78 s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
··· 21 } 22 23 func (s *Server) handleSubmitPlcOperation(e echo.Context) error { 24 + logger := s.logger.With("name", "handleIdentitySubmitPlcOperation") 25 + 26 repo := e.Get("repo").(*models.RepoActor) 27 28 var req ComAtprotoSubmitPlcOperationRequest 29 if err := e.Bind(&req); err != nil { 30 + logger.Error("error binding", "error", err) 31 return helpers.ServerError(e, nil) 32 } 33 ··· 42 43 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey) 44 if err != nil { 45 + logger.Error("error parsing key", "error", err) 46 return helpers.ServerError(e, nil) 47 } 48 required, err := s.plcClient.CreateDidCredentials(k, "", repo.Actor.Handle) 49 if err != nil { 50 + logger.Error("error crating did credentials", "error", err) 51 return helpers.ServerError(e, nil) 52 } 53 ··· 74 } 75 76 if err := s.passport.BustDoc(context.TODO(), repo.Repo.Did); err != nil { 77 + logger.Warn("error busting did doc", "error", err) 78 } 79 80 s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
+8 -6
server/handle_identity_update_handle.go
··· 22 } 23 24 func (s *Server) handleIdentityUpdateHandle(e echo.Context) error { 25 repo := e.Get("repo").(*models.RepoActor) 26 27 var req ComAtprotoIdentityUpdateHandleRequest 28 if err := e.Bind(&req); err != nil { 29 - s.logger.Error("error binding", "error", err) 30 return helpers.ServerError(e, nil) 31 } 32 ··· 41 if strings.HasPrefix(repo.Repo.Did, "did:plc:") { 42 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did) 43 if err != nil { 44 - s.logger.Error("error fetching doc", "error", err) 45 return helpers.ServerError(e, nil) 46 } 47 ··· 68 69 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey) 70 if err != nil { 71 - s.logger.Error("error parsing signing key", "error", err) 72 return helpers.ServerError(e, nil) 73 } 74 ··· 82 } 83 84 if err := s.passport.BustDoc(context.TODO(), repo.Repo.Did); err != nil { 85 - s.logger.Warn("error busting did doc", "error", err) 86 } 87 88 s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ ··· 94 }, 95 }) 96 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 return helpers.ServerError(e, nil) 100 } 101
··· 22 } 23 24 func (s *Server) handleIdentityUpdateHandle(e echo.Context) error { 25 + logger := s.logger.With("name", "handleIdentityUpdateHandle") 26 + 27 repo := e.Get("repo").(*models.RepoActor) 28 29 var req ComAtprotoIdentityUpdateHandleRequest 30 if err := e.Bind(&req); err != nil { 31 + logger.Error("error binding", "error", err) 32 return helpers.ServerError(e, nil) 33 } 34 ··· 43 if strings.HasPrefix(repo.Repo.Did, "did:plc:") { 44 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did) 45 if err != nil { 46 + logger.Error("error fetching doc", "error", err) 47 return helpers.ServerError(e, nil) 48 } 49 ··· 70 71 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey) 72 if err != nil { 73 + logger.Error("error parsing signing key", "error", err) 74 return helpers.ServerError(e, nil) 75 } 76 ··· 84 } 85 86 if err := s.passport.BustDoc(context.TODO(), repo.Repo.Did); err != nil { 87 + logger.Warn("error busting did doc", "error", err) 88 } 89 90 s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ ··· 96 }, 97 }) 98 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) 101 return helpers.ServerError(e, nil) 102 } 103
+14 -11
server/handle_import_repo.go
··· 18 ) 19 20 func (s *Server) handleRepoImportRepo(e echo.Context) error { 21 urepo := e.Get("repo").(*models.RepoActor) 22 23 b, err := io.ReadAll(e.Request().Body) 24 if err != nil { 25 - s.logger.Error("could not read bytes in import request", "error", err) 26 return helpers.ServerError(e, nil) 27 } 28 ··· 30 31 cs, err := car.NewCarReader(bytes.NewReader(b)) 32 if err != nil { 33 - s.logger.Error("could not read car in import request", "error", err) 34 return helpers.ServerError(e, nil) 35 } 36 37 orderedBlocks := []blocks.Block{} 38 currBlock, err := cs.Next() 39 if err != nil { 40 - s.logger.Error("could not get first block from car", "error", err) 41 return helpers.ServerError(e, nil) 42 } 43 currBlockCt := 1 44 45 for currBlock != nil { 46 - s.logger.Info("someone is importing their repo", "block", currBlockCt) 47 orderedBlocks = append(orderedBlocks, currBlock) 48 next, _ := cs.Next() 49 currBlock = next ··· 53 slices.Reverse(orderedBlocks) 54 55 if err := bs.PutMany(context.TODO(), orderedBlocks); err != nil { 56 - s.logger.Error("could not insert blocks", "error", err) 57 return helpers.ServerError(e, nil) 58 } 59 60 r, err := repo.OpenRepo(context.TODO(), bs, cs.Header.Roots[0]) 61 if err != nil { 62 - s.logger.Error("could not open repo", "error", err) 63 return helpers.ServerError(e, nil) 64 } 65 66 - tx := s.db.BeginDangerously() 67 68 clock := syntax.NewTIDClock(0) 69 ··· 74 cidStr := cid.String() 75 b, err := bs.Get(context.TODO(), cid) 76 if err != nil { 77 - s.logger.Error("record bytes don't exist in blockstore", "error", err) 78 return helpers.ServerError(e, nil) 79 } 80 ··· 94 return nil 95 }); err != nil { 96 tx.Rollback() 97 - s.logger.Error("record bytes don't exist in blockstore", "error", err) 98 return helpers.ServerError(e, nil) 99 } 100 ··· 102 103 root, rev, err := r.Commit(context.TODO(), urepo.SignFor) 104 if err != nil { 105 - s.logger.Error("error committing", "error", err) 106 return helpers.ServerError(e, nil) 107 } 108 109 if err := s.UpdateRepo(context.TODO(), urepo.Repo.Did, root, rev); err != nil { 110 - s.logger.Error("error updating repo after commit", "error", err) 111 return helpers.ServerError(e, nil) 112 } 113
··· 18 ) 19 20 func (s *Server) handleRepoImportRepo(e echo.Context) error { 21 + ctx := e.Request().Context() 22 + logger := s.logger.With("name", "handleImportRepo") 23 + 24 urepo := e.Get("repo").(*models.RepoActor) 25 26 b, err := io.ReadAll(e.Request().Body) 27 if err != nil { 28 + logger.Error("could not read bytes in import request", "error", err) 29 return helpers.ServerError(e, nil) 30 } 31 ··· 33 34 cs, err := car.NewCarReader(bytes.NewReader(b)) 35 if err != nil { 36 + logger.Error("could not read car in import request", "error", err) 37 return helpers.ServerError(e, nil) 38 } 39 40 orderedBlocks := []blocks.Block{} 41 currBlock, err := cs.Next() 42 if err != nil { 43 + logger.Error("could not get first block from car", "error", err) 44 return helpers.ServerError(e, nil) 45 } 46 currBlockCt := 1 47 48 for currBlock != nil { 49 + logger.Info("someone is importing their repo", "block", currBlockCt) 50 orderedBlocks = append(orderedBlocks, currBlock) 51 next, _ := cs.Next() 52 currBlock = next ··· 56 slices.Reverse(orderedBlocks) 57 58 if err := bs.PutMany(context.TODO(), orderedBlocks); err != nil { 59 + logger.Error("could not insert blocks", "error", err) 60 return helpers.ServerError(e, nil) 61 } 62 63 r, err := repo.OpenRepo(context.TODO(), bs, cs.Header.Roots[0]) 64 if err != nil { 65 + logger.Error("could not open repo", "error", err) 66 return helpers.ServerError(e, nil) 67 } 68 69 + tx := s.db.BeginDangerously(ctx) 70 71 clock := syntax.NewTIDClock(0) 72 ··· 77 cidStr := cid.String() 78 b, err := bs.Get(context.TODO(), cid) 79 if err != nil { 80 + logger.Error("record bytes don't exist in blockstore", "error", err) 81 return helpers.ServerError(e, nil) 82 } 83 ··· 97 return nil 98 }); err != nil { 99 tx.Rollback() 100 + logger.Error("record bytes don't exist in blockstore", "error", err) 101 return helpers.ServerError(e, nil) 102 } 103 ··· 105 106 root, rev, err := r.Commit(context.TODO(), urepo.SignFor) 107 if err != nil { 108 + logger.Error("error committing", "error", err) 109 return helpers.ServerError(e, nil) 110 } 111 112 if err := s.UpdateRepo(context.TODO(), urepo.Repo.Did, root, rev); err != nil { 113 + logger.Error("error updating repo after commit", "error", err) 114 return helpers.ServerError(e, nil) 115 } 116
+10 -5
server/handle_oauth_authorize.go
··· 13 ) 14 15 func (s *Server) handleOauthAuthorizeGet(e echo.Context) error { 16 reqUri := e.QueryParam("request_uri") 17 if reqUri == "" { 18 // render page for logged out dev ··· 38 } 39 40 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 { 42 return helpers.ServerError(e, to.StringPtr(err.Error())) 43 } 44 ··· 72 } 73 74 func (s *Server) handleOauthAuthorizePost(e echo.Context) error { 75 repo, _, err := s.getSessionRepoOrErr(e) 76 if err != nil { 77 return e.Redirect(303, "/account/signin") ··· 79 80 var req OauthAuthorizePostRequest 81 if err := e.Bind(&req); err != nil { 82 - s.logger.Error("error binding authorize post request", "error", err) 83 return helpers.InputError(e, nil) 84 } 85 ··· 89 } 90 91 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 { 93 return helpers.ServerError(e, to.StringPtr(err.Error())) 94 } 95 ··· 113 114 code := oauth.GenerateCode() 115 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) 118 return helpers.ServerError(e, nil) 119 } 120
··· 13 ) 14 15 func (s *Server) handleOauthAuthorizeGet(e echo.Context) error { 16 + ctx := e.Request().Context() 17 + 18 reqUri := e.QueryParam("request_uri") 19 if reqUri == "" { 20 // render page for logged out dev ··· 40 } 41 42 var req provider.OauthAuthorizationRequest 43 + if err := s.db.Raw(ctx, "SELECT * FROM oauth_authorization_requests WHERE request_id = ?", nil, reqId).Scan(&req).Error; err != nil { 44 return helpers.ServerError(e, to.StringPtr(err.Error())) 45 } 46 ··· 74 } 75 76 func (s *Server) handleOauthAuthorizePost(e echo.Context) error { 77 + ctx := e.Request().Context() 78 + logger := s.logger.With("name", "handleOauthAuthorizePost") 79 + 80 repo, _, err := s.getSessionRepoOrErr(e) 81 if err != nil { 82 return e.Redirect(303, "/account/signin") ··· 84 85 var req OauthAuthorizePostRequest 86 if err := e.Bind(&req); err != nil { 87 + logger.Error("error binding authorize post request", "error", err) 88 return helpers.InputError(e, nil) 89 } 90 ··· 94 } 95 96 var authReq provider.OauthAuthorizationRequest 97 + if err := s.db.Raw(ctx, "SELECT * FROM oauth_authorization_requests WHERE request_id = ?", nil, reqId).Scan(&authReq).Error; err != nil { 98 return helpers.ServerError(e, to.StringPtr(err.Error())) 99 } 100 ··· 118 119 code := oauth.GenerateCode() 120 121 + 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 { 122 + logger.Error("error updating authorization request", "error", err) 123 return helpers.ServerError(e, nil) 124 } 125
+11 -8
server/handle_oauth_par.go
··· 19 } 20 21 func (s *Server) handleOauthPar(e echo.Context) error { 22 var parRequest provider.ParRequest 23 if err := e.Bind(&parRequest); err != nil { 24 - s.logger.Error("error binding for par request", "error", err) 25 return helpers.ServerError(e, nil) 26 } 27 28 if err := e.Validate(parRequest); err != nil { 29 - s.logger.Error("missing parameters for par request", "error", err) 30 return helpers.InputError(e, nil) 31 } 32 ··· 43 "error": "use_dpop_nonce", 44 }) 45 } 46 - s.logger.Error("error getting dpop proof", "error", err) 47 return helpers.InputError(e, nil) 48 } 49 ··· 53 AllowMissingDpopProof: true, 54 }) 55 if err != nil { 56 - s.logger.Error("error authenticating client", "client_id", parRequest.ClientID, "error", err) 57 return helpers.InputError(e, to.StringPtr(err.Error())) 58 } 59 ··· 64 } else { 65 if !client.Metadata.DpopBoundAccessTokens { 66 msg := "dpop bound access tokens are not enabled for this client" 67 - s.logger.Error(msg) 68 return helpers.InputError(e, &msg) 69 } 70 71 if dpopProof.JKT != *parRequest.DpopJkt { 72 msg := "supplied dpop jkt does not match header dpop jkt" 73 - s.logger.Error(msg) 74 return helpers.InputError(e, &msg) 75 } 76 } ··· 86 ExpiresAt: eat, 87 } 88 89 - if err := s.db.Create(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
··· 19 } 20 21 func (s *Server) handleOauthPar(e echo.Context) error { 22 + ctx := e.Request().Context() 23 + logger := s.logger.With("name", "handleOauthPar") 24 + 25 var parRequest provider.ParRequest 26 if err := e.Bind(&parRequest); err != nil { 27 + logger.Error("error binding for par request", "error", err) 28 return helpers.ServerError(e, nil) 29 } 30 31 if err := e.Validate(parRequest); err != nil { 32 + logger.Error("missing parameters for par request", "error", err) 33 return helpers.InputError(e, nil) 34 } 35 ··· 46 "error": "use_dpop_nonce", 47 }) 48 } 49 + logger.Error("error getting dpop proof", "error", err) 50 return helpers.InputError(e, nil) 51 } 52 ··· 56 AllowMissingDpopProof: true, 57 }) 58 if err != nil { 59 + logger.Error("error authenticating client", "client_id", parRequest.ClientID, "error", err) 60 return helpers.InputError(e, to.StringPtr(err.Error())) 61 } 62 ··· 67 } else { 68 if !client.Metadata.DpopBoundAccessTokens { 69 msg := "dpop bound access tokens are not enabled for this client" 70 + logger.Error(msg) 71 return helpers.InputError(e, &msg) 72 } 73 74 if dpopProof.JKT != *parRequest.DpopJkt { 75 msg := "supplied dpop jkt does not match header dpop jkt" 76 + logger.Error(msg) 77 return helpers.InputError(e, &msg) 78 } 79 } ··· 89 ExpiresAt: eat, 90 } 91 92 + if err := s.db.Create(ctx, authRequest, nil).Error; err != nil { 93 + logger.Error("error creating auth request in db", "error", err) 94 return helpers.ServerError(e, nil) 95 } 96
+16 -13
server/handle_oauth_token.go
··· 38 } 39 40 func (s *Server) handleOauthToken(e echo.Context) error { 41 var req OauthTokenRequest 42 if err := e.Bind(&req); err != nil { 43 - s.logger.Error("error binding token request", "error", err) 44 return helpers.ServerError(e, nil) 45 } 46 ··· 56 "error": "use_dpop_nonce", 57 }) 58 } 59 - s.logger.Error("error getting dpop proof", "error", err) 60 return helpers.InputError(e, nil) 61 } 62 ··· 64 AllowMissingDpopProof: true, 65 }) 66 if err != nil { 67 - s.logger.Error("error authenticating client", "client_id", req.ClientID, "error", err) 68 return helpers.InputError(e, to.StringPtr(err.Error())) 69 } 70 ··· 84 85 var authReq provider.OauthAuthorizationRequest 86 // 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) 89 return helpers.ServerError(e, nil) 90 } 91 ··· 110 case "S256": 111 inputChal, err := base64.RawURLEncoding.DecodeString(*authReq.Parameters.CodeChallenge) 112 if err != nil { 113 - s.logger.Error("error decoding code challenge", "error", err) 114 return helpers.ServerError(e, nil) 115 } 116 ··· 128 return helpers.InputError(e, to.StringPtr("code_challenge parameter wasn't provided")) 129 } 130 131 - repo, err := s.getRepoActorByDid(*authReq.Sub) 132 if err != nil { 133 helpers.InputError(e, to.StringPtr("unable to find actor")) 134 } ··· 159 return err 160 } 161 162 - if err := s.db.Create(&provider.OauthToken{ 163 ClientId: authReq.ClientId, 164 ClientAuth: *clientAuth, 165 Parameters: authReq.Parameters, ··· 171 RefreshToken: refreshToken, 172 Ip: authReq.Ip, 173 }, nil).Error; err != nil { 174 - s.logger.Error("error creating token in db", "error", err) 175 return helpers.ServerError(e, nil) 176 } 177 ··· 199 } 200 201 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) 204 return helpers.ServerError(e, nil) 205 } 206 ··· 257 return err 258 } 259 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) 262 return helpers.ServerError(e, nil) 263 } 264
··· 38 } 39 40 func (s *Server) handleOauthToken(e echo.Context) error { 41 + ctx := e.Request().Context() 42 + logger := s.logger.With("name", "handleOauthToken") 43 + 44 var req OauthTokenRequest 45 if err := e.Bind(&req); err != nil { 46 + logger.Error("error binding token request", "error", err) 47 return helpers.ServerError(e, nil) 48 } 49 ··· 59 "error": "use_dpop_nonce", 60 }) 61 } 62 + logger.Error("error getting dpop proof", "error", err) 63 return helpers.InputError(e, nil) 64 } 65 ··· 67 AllowMissingDpopProof: true, 68 }) 69 if err != nil { 70 + logger.Error("error authenticating client", "client_id", req.ClientID, "error", err) 71 return helpers.InputError(e, to.StringPtr(err.Error())) 72 } 73 ··· 87 88 var authReq provider.OauthAuthorizationRequest 89 // get the lil guy and delete him 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) 92 return helpers.ServerError(e, nil) 93 } 94 ··· 113 case "S256": 114 inputChal, err := base64.RawURLEncoding.DecodeString(*authReq.Parameters.CodeChallenge) 115 if err != nil { 116 + logger.Error("error decoding code challenge", "error", err) 117 return helpers.ServerError(e, nil) 118 } 119 ··· 131 return helpers.InputError(e, to.StringPtr("code_challenge parameter wasn't provided")) 132 } 133 134 + repo, err := s.getRepoActorByDid(ctx, *authReq.Sub) 135 if err != nil { 136 helpers.InputError(e, to.StringPtr("unable to find actor")) 137 } ··· 162 return err 163 } 164 165 + if err := s.db.Create(ctx, &provider.OauthToken{ 166 ClientId: authReq.ClientId, 167 ClientAuth: *clientAuth, 168 Parameters: authReq.Parameters, ··· 174 RefreshToken: refreshToken, 175 Ip: authReq.Ip, 176 }, nil).Error; err != nil { 177 + logger.Error("error creating token in db", "error", err) 178 return helpers.ServerError(e, nil) 179 } 180 ··· 202 } 203 204 var oauthToken provider.OauthToken 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) 207 return helpers.ServerError(e, nil) 208 } 209 ··· 260 return err 261 } 262 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) 265 return helpers.ServerError(e, nil) 266 } 267
+6 -6
server/handle_proxy.go
··· 47 } 48 49 func (s *Server) handleProxy(e echo.Context) error { 50 - lgr := s.logger.With("handler", "handleProxy") 51 52 repo, isAuthed := e.Get("repo").(*models.RepoActor) 53 ··· 58 59 endpoint, svcDid, err := s.getAtprotoProxyEndpointFromRequest(e) 60 if err != nil { 61 - lgr.Error("could not get atproto proxy", "error", err) 62 return helpers.ServerError(e, nil) 63 } 64 ··· 90 } 91 hj, err := json.Marshal(header) 92 if err != nil { 93 - lgr.Error("error marshaling header", "error", err) 94 return helpers.ServerError(e, nil) 95 } 96 ··· 118 } 119 pj, err := json.Marshal(payload) 120 if err != nil { 121 - lgr.Error("error marashaling payload", "error", err) 122 return helpers.ServerError(e, nil) 123 } 124 ··· 129 130 sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey) 131 if err != nil { 132 - lgr.Error("can't load private key", "error", err) 133 return err 134 } 135 136 R, S, _, err := sk.SignRaw(rand.Reader, hash[:]) 137 if err != nil { 138 - lgr.Error("error signing", "error", err) 139 } 140 141 rBytes := R.Bytes()
··· 47 } 48 49 func (s *Server) handleProxy(e echo.Context) error { 50 + logger := s.logger.With("handler", "handleProxy") 51 52 repo, isAuthed := e.Get("repo").(*models.RepoActor) 53 ··· 58 59 endpoint, svcDid, err := s.getAtprotoProxyEndpointFromRequest(e) 60 if err != nil { 61 + logger.Error("could not get atproto proxy", "error", err) 62 return helpers.ServerError(e, nil) 63 } 64 ··· 90 } 91 hj, err := json.Marshal(header) 92 if err != nil { 93 + logger.Error("error marshaling header", "error", err) 94 return helpers.ServerError(e, nil) 95 } 96 ··· 118 } 119 pj, err := json.Marshal(payload) 120 if err != nil { 121 + logger.Error("error marashaling payload", "error", err) 122 return helpers.ServerError(e, nil) 123 } 124 ··· 129 130 sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey) 131 if err != nil { 132 + logger.Error("can't load private key", "error", err) 133 return err 134 } 135 136 R, S, _, err := sk.SignRaw(rand.Reader, hash[:]) 137 if err != nil { 138 + logger.Error("error signing", "error", err) 139 } 140 141 rBytes := R.Bytes()
+14 -11
server/handle_repo_apply_writes.go
··· 6 "github.com/labstack/echo/v4" 7 ) 8 9 - type ComAtprotoRepoApplyWritesRequest struct { 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 Validate *bool `json:"bool,omitempty"` 12 Writes []ComAtprotoRepoApplyWritesItem `json:"writes"` ··· 20 Value *MarshalableMap `json:"value,omitempty"` 21 } 22 23 - type ComAtprotoRepoApplyWritesResponse struct { 24 Commit RepoCommit `json:"commit"` 25 Results []ApplyWriteResult `json:"results"` 26 } 27 28 func (s *Server) handleApplyWrites(e echo.Context) error { 29 - repo := e.Get("repo").(*models.RepoActor) 30 31 - var req ComAtprotoRepoApplyWritesRequest 32 if err := e.Bind(&req); err != nil { 33 - s.logger.Error("error binding", "error", err) 34 return helpers.ServerError(e, nil) 35 } 36 37 if err := e.Validate(req); err != nil { 38 - s.logger.Error("error validating", "error", err) 39 return helpers.InputError(e, nil) 40 } 41 42 if repo.Repo.Did != req.Repo { 43 - s.logger.Warn("mismatched repo/auth") 44 return helpers.InputError(e, nil) 45 } 46 47 - ops := []Op{} 48 for _, item := range req.Writes { 49 ops = append(ops, Op{ 50 Type: OpType(item.Type), ··· 54 }) 55 } 56 57 - results, err := s.repoman.applyWrites(repo.Repo, ops, req.SwapCommit) 58 if err != nil { 59 - s.logger.Error("error applying writes", "error", err) 60 return helpers.ServerError(e, nil) 61 } 62 ··· 66 results[i].Commit = nil 67 } 68 69 - return e.JSON(200, ComAtprotoRepoApplyWritesResponse{ 70 Commit: commit, 71 Results: results, 72 })
··· 6 "github.com/labstack/echo/v4" 7 ) 8 9 + type ComAtprotoRepoApplyWritesInput struct { 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 Validate *bool `json:"bool,omitempty"` 12 Writes []ComAtprotoRepoApplyWritesItem `json:"writes"` ··· 20 Value *MarshalableMap `json:"value,omitempty"` 21 } 22 23 + type ComAtprotoRepoApplyWritesOutput struct { 24 Commit RepoCommit `json:"commit"` 25 Results []ApplyWriteResult `json:"results"` 26 } 27 28 func (s *Server) handleApplyWrites(e echo.Context) error { 29 + ctx := e.Request().Context() 30 + logger := s.logger.With("name", "handleRepoApplyWrites") 31 32 + var req ComAtprotoRepoApplyWritesInput 33 if err := e.Bind(&req); err != nil { 34 + logger.Error("error binding", "error", err) 35 return helpers.ServerError(e, nil) 36 } 37 38 if err := e.Validate(req); err != nil { 39 + logger.Error("error validating", "error", err) 40 return helpers.InputError(e, nil) 41 } 42 43 + repo := e.Get("repo").(*models.RepoActor) 44 + 45 if repo.Repo.Did != req.Repo { 46 + logger.Warn("mismatched repo/auth") 47 return helpers.InputError(e, nil) 48 } 49 50 + ops := make([]Op, 0, len(req.Writes)) 51 for _, item := range req.Writes { 52 ops = append(ops, Op{ 53 Type: OpType(item.Type), ··· 57 }) 58 } 59 60 + results, err := s.repoman.applyWrites(ctx, repo.Repo, ops, req.SwapCommit) 61 if err != nil { 62 + logger.Error("error applying writes", "error", err) 63 return helpers.ServerError(e, nil) 64 } 65 ··· 69 results[i].Commit = nil 70 } 71 72 + return e.JSON(200, ComAtprotoRepoApplyWritesOutput{ 73 Commit: commit, 74 Results: results, 75 })
+10 -7
server/handle_repo_create_record.go
··· 6 "github.com/labstack/echo/v4" 7 ) 8 9 - type ComAtprotoRepoCreateRecordRequest struct { 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 Collection string `json:"collection" validate:"required,atproto-nsid"` 12 Rkey *string `json:"rkey,omitempty"` ··· 17 } 18 19 func (s *Server) handleCreateRecord(e echo.Context) error { 20 repo := e.Get("repo").(*models.RepoActor) 21 22 - var req ComAtprotoRepoCreateRecordRequest 23 if err := e.Bind(&req); err != nil { 24 - s.logger.Error("error binding", "error", err) 25 return helpers.ServerError(e, nil) 26 } 27 28 if err := e.Validate(req); err != nil { 29 - s.logger.Error("error validating", "error", err) 30 return helpers.InputError(e, nil) 31 } 32 33 if repo.Repo.Did != req.Repo { 34 - s.logger.Warn("mismatched repo/auth") 35 return helpers.InputError(e, nil) 36 } 37 ··· 40 optype = OpTypeUpdate 41 } 42 43 - results, err := s.repoman.applyWrites(repo.Repo, []Op{ 44 { 45 Type: optype, 46 Collection: req.Collection, ··· 51 }, 52 }, req.SwapCommit) 53 if err != nil { 54 - s.logger.Error("error applying writes", "error", err) 55 return helpers.ServerError(e, nil) 56 } 57
··· 6 "github.com/labstack/echo/v4" 7 ) 8 9 + type ComAtprotoRepoCreateRecordInput struct { 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 Collection string `json:"collection" validate:"required,atproto-nsid"` 12 Rkey *string `json:"rkey,omitempty"` ··· 17 } 18 19 func (s *Server) handleCreateRecord(e echo.Context) error { 20 + ctx := e.Request().Context() 21 + logger := s.logger.With("name", "handleCreateRecord") 22 + 23 repo := e.Get("repo").(*models.RepoActor) 24 25 + var req ComAtprotoRepoCreateRecordInput 26 if err := e.Bind(&req); err != nil { 27 + logger.Error("error binding", "error", err) 28 return helpers.ServerError(e, nil) 29 } 30 31 if err := e.Validate(req); err != nil { 32 + logger.Error("error validating", "error", err) 33 return helpers.InputError(e, nil) 34 } 35 36 if repo.Repo.Did != req.Repo { 37 + logger.Warn("mismatched repo/auth") 38 return helpers.InputError(e, nil) 39 } 40 ··· 43 optype = OpTypeUpdate 44 } 45 46 + results, err := s.repoman.applyWrites(ctx, repo.Repo, []Op{ 47 { 48 Type: optype, 49 Collection: req.Collection, ··· 54 }, 55 }, req.SwapCommit) 56 if err != nil { 57 + logger.Error("error applying writes", "error", err) 58 return helpers.ServerError(e, nil) 59 } 60
+10 -7
server/handle_repo_delete_record.go
··· 6 "github.com/labstack/echo/v4" 7 ) 8 9 - type ComAtprotoRepoDeleteRecordRequest struct { 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 Collection string `json:"collection" validate:"required,atproto-nsid"` 12 Rkey string `json:"rkey" validate:"required,atproto-rkey"` ··· 15 } 16 17 func (s *Server) handleDeleteRecord(e echo.Context) error { 18 repo := e.Get("repo").(*models.RepoActor) 19 20 - var req ComAtprotoRepoDeleteRecordRequest 21 if err := e.Bind(&req); err != nil { 22 - s.logger.Error("error binding", "error", err) 23 return helpers.ServerError(e, nil) 24 } 25 26 if err := e.Validate(req); err != nil { 27 - s.logger.Error("error validating", "error", err) 28 return helpers.InputError(e, nil) 29 } 30 31 if repo.Repo.Did != req.Repo { 32 - s.logger.Warn("mismatched repo/auth") 33 return helpers.InputError(e, nil) 34 } 35 36 - results, err := s.repoman.applyWrites(repo.Repo, []Op{ 37 { 38 Type: OpTypeDelete, 39 Collection: req.Collection, ··· 42 }, 43 }, req.SwapCommit) 44 if err != nil { 45 - s.logger.Error("error applying writes", "error", err) 46 return helpers.ServerError(e, nil) 47 } 48
··· 6 "github.com/labstack/echo/v4" 7 ) 8 9 + type ComAtprotoRepoDeleteRecordInput struct { 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 Collection string `json:"collection" validate:"required,atproto-nsid"` 12 Rkey string `json:"rkey" validate:"required,atproto-rkey"` ··· 15 } 16 17 func (s *Server) handleDeleteRecord(e echo.Context) error { 18 + ctx := e.Request().Context() 19 + logger := s.logger.With("name", "handleDeleteRecord") 20 + 21 repo := e.Get("repo").(*models.RepoActor) 22 23 + var req ComAtprotoRepoDeleteRecordInput 24 if err := e.Bind(&req); err != nil { 25 + logger.Error("error binding", "error", err) 26 return helpers.ServerError(e, nil) 27 } 28 29 if err := e.Validate(req); err != nil { 30 + logger.Error("error validating", "error", err) 31 return helpers.InputError(e, nil) 32 } 33 34 if repo.Repo.Did != req.Repo { 35 + logger.Warn("mismatched repo/auth") 36 return helpers.InputError(e, nil) 37 } 38 39 + results, err := s.repoman.applyWrites(ctx, repo.Repo, []Op{ 40 { 41 Type: OpTypeDelete, 42 Collection: req.Collection, ··· 45 }, 46 }, req.SwapCommit) 47 if err != nil { 48 + logger.Error("error applying writes", "error", err) 49 return helpers.ServerError(e, nil) 50 } 51
+8 -5
server/handle_repo_describe_repo.go
··· 20 } 21 22 func (s *Server) handleDescribeRepo(e echo.Context) error { 23 did := e.QueryParam("repo") 24 - repo, err := s.getRepoActorByDid(did) 25 if err != nil { 26 if err == gorm.ErrRecordNotFound { 27 return helpers.InputError(e, to.StringPtr("RepoNotFound")) 28 } 29 30 - s.logger.Error("error looking up repo", "error", err) 31 return helpers.ServerError(e, nil) 32 } 33 ··· 35 36 diddoc, err := s.passport.FetchDoc(e.Request().Context(), repo.Repo.Did) 37 if err != nil { 38 - s.logger.Error("error fetching diddoc", "error", err) 39 return helpers.ServerError(e, nil) 40 } 41 ··· 64 } 65 66 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) 69 return helpers.ServerError(e, nil) 70 } 71
··· 20 } 21 22 func (s *Server) handleDescribeRepo(e echo.Context) error { 23 + ctx := e.Request().Context() 24 + logger := s.logger.With("name", "handleDescribeRepo") 25 + 26 did := e.QueryParam("repo") 27 + repo, err := s.getRepoActorByDid(ctx, did) 28 if err != nil { 29 if err == gorm.ErrRecordNotFound { 30 return helpers.InputError(e, to.StringPtr("RepoNotFound")) 31 } 32 33 + logger.Error("error looking up repo", "error", err) 34 return helpers.ServerError(e, nil) 35 } 36 ··· 38 39 diddoc, err := s.passport.FetchDoc(e.Request().Context(), repo.Repo.Did) 40 if err != nil { 41 + logger.Error("error fetching diddoc", "error", err) 42 return helpers.ServerError(e, nil) 43 } 44 ··· 67 } 68 69 var records []models.Record 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) 72 return helpers.ServerError(e, nil) 73 } 74
+3 -1
server/handle_repo_get_record.go
··· 14 } 15 16 func (s *Server) handleRepoGetRecord(e echo.Context) error { 17 repo := e.QueryParam("repo") 18 collection := e.QueryParam("collection") 19 rkey := e.QueryParam("rkey") ··· 32 } 33 34 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 { 36 // TODO: handle error nicely 37 return err 38 }
··· 14 } 15 16 func (s *Server) handleRepoGetRecord(e echo.Context) error { 17 + ctx := e.Request().Context() 18 + 19 repo := e.QueryParam("repo") 20 collection := e.QueryParam("collection") 21 rkey := e.QueryParam("rkey") ··· 34 } 35 36 var record models.Record 37 + if err := s.db.Raw(ctx, "SELECT * FROM records WHERE did = ? AND nsid = ? AND rkey = ?"+cidquery, nil, params...).Scan(&record).Error; err != nil { 38 // TODO: handle error nicely 39 return err 40 }
+6 -3
server/handle_repo_list_missing_blobs.go
··· 22 } 23 24 func (s *Server) handleListMissingBlobs(e echo.Context) error { 25 urepo := e.Get("repo").(*models.RepoActor) 26 27 limitStr := e.QueryParam("limit") ··· 35 } 36 37 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) 40 return helpers.ServerError(e, nil) 41 } 42 ··· 69 } 70 71 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 { 73 continue 74 } 75
··· 22 } 23 24 func (s *Server) handleListMissingBlobs(e echo.Context) error { 25 + ctx := e.Request().Context() 26 + logger := s.logger.With("name", "handleListMissingBlos") 27 + 28 urepo := e.Get("repo").(*models.RepoActor) 29 30 limitStr := e.QueryParam("limit") ··· 38 } 39 40 var records []models.Record 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) 43 return helpers.ServerError(e, nil) 44 } 45 ··· 72 } 73 74 var count int64 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 { 76 continue 77 } 78
+7 -4
server/handle_repo_list_records.go
··· 46 } 47 48 func (s *Server) handleListRecords(e echo.Context) error { 49 var req ComAtprotoRepoListRecordsRequest 50 if err := e.Bind(&req); err != nil { 51 - s.logger.Error("could not bind list records request", "error", err) 52 return helpers.ServerError(e, nil) 53 } 54 ··· 78 79 did := req.Repo 80 if _, err := syntax.ParseDID(did); err != nil { 81 - actor, err := s.getActorByHandle(req.Repo) 82 if err != nil { 83 return helpers.InputError(e, to.StringPtr("RepoNotFound")) 84 } ··· 93 params = append(params, limit) 94 95 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) 98 return helpers.ServerError(e, nil) 99 } 100
··· 46 } 47 48 func (s *Server) handleListRecords(e echo.Context) error { 49 + ctx := e.Request().Context() 50 + logger := s.logger.With("name", "handleListRecords") 51 + 52 var req ComAtprotoRepoListRecordsRequest 53 if err := e.Bind(&req); err != nil { 54 + logger.Error("could not bind list records request", "error", err) 55 return helpers.ServerError(e, nil) 56 } 57 ··· 81 82 did := req.Repo 83 if _, err := syntax.ParseDID(did); err != nil { 84 + actor, err := s.getActorByHandle(ctx, req.Repo) 85 if err != nil { 86 return helpers.InputError(e, to.StringPtr("RepoNotFound")) 87 } ··· 96 params = append(params, limit) 97 98 var records []models.Record 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) 101 return helpers.ServerError(e, nil) 102 } 103
+3 -1
server/handle_repo_list_repos.go
··· 21 22 // TODO: paginate this bitch 23 func (s *Server) handleListRepos(e echo.Context) error { 24 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 { 26 return err 27 } 28
··· 21 22 // TODO: paginate this bitch 23 func (s *Server) handleListRepos(e echo.Context) error { 24 + ctx := e.Request().Context() 25 + 26 var repos []models.Repo 27 + if err := s.db.Raw(ctx, "SELECT * FROM repos ORDER BY created_at DESC LIMIT 500", nil).Scan(&repos).Error; err != nil { 28 return err 29 } 30
+10 -7
server/handle_repo_put_record.go
··· 6 "github.com/labstack/echo/v4" 7 ) 8 9 - type ComAtprotoRepoPutRecordRequest struct { 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 Collection string `json:"collection" validate:"required,atproto-nsid"` 12 Rkey string `json:"rkey" validate:"required,atproto-rkey"` ··· 17 } 18 19 func (s *Server) handlePutRecord(e echo.Context) error { 20 repo := e.Get("repo").(*models.RepoActor) 21 22 - var req ComAtprotoRepoPutRecordRequest 23 if err := e.Bind(&req); err != nil { 24 - s.logger.Error("error binding", "error", err) 25 return helpers.ServerError(e, nil) 26 } 27 28 if err := e.Validate(req); err != nil { 29 - s.logger.Error("error validating", "error", err) 30 return helpers.InputError(e, nil) 31 } 32 33 if repo.Repo.Did != req.Repo { 34 - s.logger.Warn("mismatched repo/auth") 35 return helpers.InputError(e, nil) 36 } 37 ··· 40 optype = OpTypeUpdate 41 } 42 43 - results, err := s.repoman.applyWrites(repo.Repo, []Op{ 44 { 45 Type: optype, 46 Collection: req.Collection, ··· 51 }, 52 }, req.SwapCommit) 53 if err != nil { 54 - s.logger.Error("error applying writes", "error", err) 55 return helpers.ServerError(e, nil) 56 } 57
··· 6 "github.com/labstack/echo/v4" 7 ) 8 9 + type ComAtprotoRepoPutRecordInput struct { 10 Repo string `json:"repo" validate:"required,atproto-did"` 11 Collection string `json:"collection" validate:"required,atproto-nsid"` 12 Rkey string `json:"rkey" validate:"required,atproto-rkey"` ··· 17 } 18 19 func (s *Server) handlePutRecord(e echo.Context) error { 20 + ctx := e.Request().Context() 21 + logger := s.logger.With("name", "handlePutRecord") 22 + 23 repo := e.Get("repo").(*models.RepoActor) 24 25 + var req ComAtprotoRepoPutRecordInput 26 if err := e.Bind(&req); err != nil { 27 + logger.Error("error binding", "error", err) 28 return helpers.ServerError(e, nil) 29 } 30 31 if err := e.Validate(req); err != nil { 32 + logger.Error("error validating", "error", err) 33 return helpers.InputError(e, nil) 34 } 35 36 if repo.Repo.Did != req.Repo { 37 + logger.Warn("mismatched repo/auth") 38 return helpers.InputError(e, nil) 39 } 40 ··· 43 optype = OpTypeUpdate 44 } 45 46 + results, err := s.repoman.applyWrites(ctx, repo.Repo, []Op{ 47 { 48 Type: optype, 49 Collection: req.Collection, ··· 54 }, 55 }, req.SwapCommit) 56 if err != nil { 57 + logger.Error("error applying writes", "error", err) 58 return helpers.ServerError(e, nil) 59 } 60
+13 -10
server/handle_repo_upload_blob.go
··· 32 } 33 34 func (s *Server) handleRepoUploadBlob(e echo.Context) error { 35 urepo := e.Get("repo").(*models.RepoActor) 36 37 mime := e.Request().Header.Get("content-type") ··· 51 Storage: storage, 52 } 53 54 - if err := s.db.Create(&blob, nil).Error; err != nil { 55 - s.logger.Error("error creating new blob in db", "error", err) 56 return helpers.ServerError(e, nil) 57 } 58 ··· 69 break 70 } 71 } else if err != nil && err != io.ErrUnexpectedEOF { 72 - s.logger.Error("error reading blob", "error", err) 73 return helpers.ServerError(e, nil) 74 } 75 ··· 84 Data: data, 85 } 86 87 - if err := s.db.Create(&blobPart, nil).Error; err != nil { 88 - s.logger.Error("error adding blob part to db", "error", err) 89 return helpers.ServerError(e, nil) 90 } 91 } ··· 98 99 c, err := cid.NewPrefixV1(cid.Raw, multihash.SHA2_256).Sum(fulldata.Bytes()) 100 if err != nil { 101 - s.logger.Error("error creating cid prefix", "error", err) 102 return helpers.ServerError(e, nil) 103 } 104 ··· 115 116 sess, err := session.NewSession(config) 117 if err != nil { 118 - s.logger.Error("error creating aws session", "error", err) 119 return helpers.ServerError(e, nil) 120 } 121 ··· 126 Key: aws.String(fmt.Sprintf("blobs/%s/%s", urepo.Repo.Did, c.String())), 127 Body: bytes.NewReader(fulldata.Bytes()), 128 }); err != nil { 129 - s.logger.Error("error uploading blob to s3", "error", err) 130 return helpers.ServerError(e, nil) 131 } 132 } 133 134 - if err := s.db.Exec("UPDATE blobs SET cid = ? WHERE id = ?", nil, c.Bytes(), blob.ID).Error; err != nil { 135 // there should probably be somme handling here if this fails... 136 - s.logger.Error("error updating blob", "error", err) 137 return helpers.ServerError(e, nil) 138 } 139
··· 32 } 33 34 func (s *Server) handleRepoUploadBlob(e echo.Context) error { 35 + ctx := e.Request().Context() 36 + logger := s.logger.With("name", "handleRepoUploadBlob") 37 + 38 urepo := e.Get("repo").(*models.RepoActor) 39 40 mime := e.Request().Header.Get("content-type") ··· 54 Storage: storage, 55 } 56 57 + if err := s.db.Create(ctx, &blob, nil).Error; err != nil { 58 + logger.Error("error creating new blob in db", "error", err) 59 return helpers.ServerError(e, nil) 60 } 61 ··· 72 break 73 } 74 } else if err != nil && err != io.ErrUnexpectedEOF { 75 + logger.Error("error reading blob", "error", err) 76 return helpers.ServerError(e, nil) 77 } 78 ··· 87 Data: data, 88 } 89 90 + if err := s.db.Create(ctx, &blobPart, nil).Error; err != nil { 91 + logger.Error("error adding blob part to db", "error", err) 92 return helpers.ServerError(e, nil) 93 } 94 } ··· 101 102 c, err := cid.NewPrefixV1(cid.Raw, multihash.SHA2_256).Sum(fulldata.Bytes()) 103 if err != nil { 104 + logger.Error("error creating cid prefix", "error", err) 105 return helpers.ServerError(e, nil) 106 } 107 ··· 118 119 sess, err := session.NewSession(config) 120 if err != nil { 121 + logger.Error("error creating aws session", "error", err) 122 return helpers.ServerError(e, nil) 123 } 124 ··· 129 Key: aws.String(fmt.Sprintf("blobs/%s/%s", urepo.Repo.Did, c.String())), 130 Body: bytes.NewReader(fulldata.Bytes()), 131 }); err != nil { 132 + logger.Error("error uploading blob to s3", "error", err) 133 return helpers.ServerError(e, nil) 134 } 135 } 136 137 + if err := s.db.Exec(ctx, "UPDATE blobs SET cid = ? WHERE id = ?", nil, c.Bytes(), blob.ID).Error; err != nil { 138 // there should probably be somme handling here if this fails... 139 + logger.Error("error updating blob", "error", err) 140 return helpers.ServerError(e, nil) 141 } 142
+6 -3
server/handle_server_activate_account.go
··· 18 } 19 20 func (s *Server) handleServerActivateAccount(e echo.Context) error { 21 var req ComAtprotoServerDeactivateAccountRequest 22 if err := e.Bind(&req); err != nil { 23 - s.logger.Error("error binding", "error", err) 24 return helpers.ServerError(e, nil) 25 } 26 27 urepo := e.Get("repo").(*models.RepoActor) 28 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) 31 return helpers.ServerError(e, nil) 32 } 33
··· 18 } 19 20 func (s *Server) handleServerActivateAccount(e echo.Context) error { 21 + ctx := e.Request().Context() 22 + logger := s.logger.With("name", "handleServerActivateAccount") 23 + 24 var req ComAtprotoServerDeactivateAccountRequest 25 if err := e.Bind(&req); err != nil { 26 + logger.Error("error binding", "error", err) 27 return helpers.ServerError(e, nil) 28 } 29 30 urepo := e.Get("repo").(*models.RepoActor) 31 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) 34 return helpers.ServerError(e, nil) 35 } 36
+10 -7
server/handle_server_check_account_status.go
··· 20 } 21 22 func (s *Server) handleServerCheckAccountStatus(e echo.Context) error { 23 urepo := e.Get("repo").(*models.RepoActor) 24 25 resp := ComAtprotoServerCheckAccountStatusResponse{ ··· 31 32 rootcid, err := cid.Cast(urepo.Root) 33 if err != nil { 34 - s.logger.Error("error casting cid", "error", err) 35 return helpers.ServerError(e, nil) 36 } 37 resp.RepoCommit = rootcid.String() ··· 41 } 42 43 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) 46 return helpers.ServerError(e, nil) 47 } 48 resp.RepoBlocks = blockCtResp.Ct 49 50 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) 53 return helpers.ServerError(e, nil) 54 } 55 resp.IndexedRecords = recCtResp.Ct 56 57 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) 60 return helpers.ServerError(e, nil) 61 } 62 resp.ExpectedBlobs = blobCtResp.Ct
··· 20 } 21 22 func (s *Server) handleServerCheckAccountStatus(e echo.Context) error { 23 + ctx := e.Request().Context() 24 + logger := s.logger.With("name", "handleServerCheckAccountStatus") 25 + 26 urepo := e.Get("repo").(*models.RepoActor) 27 28 resp := ComAtprotoServerCheckAccountStatusResponse{ ··· 34 35 rootcid, err := cid.Cast(urepo.Root) 36 if err != nil { 37 + logger.Error("error casting cid", "error", err) 38 return helpers.ServerError(e, nil) 39 } 40 resp.RepoCommit = rootcid.String() ··· 44 } 45 46 var blockCtResp CountResp 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) 49 return helpers.ServerError(e, nil) 50 } 51 resp.RepoBlocks = blockCtResp.Ct 52 53 var recCtResp CountResp 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) 56 return helpers.ServerError(e, nil) 57 } 58 resp.IndexedRecords = recCtResp.Ct 59 60 var blobCtResp CountResp 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) 63 return helpers.ServerError(e, nil) 64 } 65 resp.ExpectedBlobs = blobCtResp.Ct
+6 -3
server/handle_server_confirm_email.go
··· 15 } 16 17 func (s *Server) handleServerConfirmEmail(e echo.Context) error { 18 urepo := e.Get("repo").(*models.RepoActor) 19 20 var req ComAtprotoServerConfirmEmailRequest 21 if err := e.Bind(&req); err != nil { 22 - s.logger.Error("error binding", "error", err) 23 return helpers.ServerError(e, nil) 24 } 25 ··· 41 42 now := time.Now().UTC() 43 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) 46 return helpers.ServerError(e, nil) 47 } 48
··· 15 } 16 17 func (s *Server) handleServerConfirmEmail(e echo.Context) error { 18 + ctx := e.Request().Context() 19 + logger := s.logger.With("name", "handleServerConfirmEmail") 20 + 21 urepo := e.Get("repo").(*models.RepoActor) 22 23 var req ComAtprotoServerConfirmEmailRequest 24 if err := e.Bind(&req); err != nil { 25 + logger.Error("error binding", "error", err) 26 return helpers.ServerError(e, nil) 27 } 28 ··· 44 45 now := time.Now().UTC() 46 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) 49 return helpers.ServerError(e, nil) 50 } 51
+39 -36
server/handle_server_create_account.go
··· 36 } 37 38 func (s *Server) handleCreateAccount(e echo.Context) error { 39 var request ComAtprotoServerCreateAccountRequest 40 41 if err := e.Bind(&request); err != nil { 42 - s.logger.Error("error receiving request", "endpoint", "com.atproto.server.createAccount", "error", err) 43 return helpers.ServerError(e, nil) 44 } 45 46 request.Handle = strings.ToLower(request.Handle) 47 48 if err := e.Validate(request); err != nil { 49 - s.logger.Error("error validating request", "endpoint", "com.atproto.server.createAccount", "error", err) 50 51 var verr ValidationError 52 if errors.As(err, &verr) { ··· 68 } 69 } 70 } 71 - 72 var signupDid string 73 if request.Did != nil { 74 - signupDid = *request.Did; 75 - 76 token := strings.TrimSpace(strings.Replace(e.Request().Header.Get("authorization"), "Bearer ", "", 1)) 77 if token == "" { 78 return helpers.UnauthorizedError(e, to.StringPtr("must authenticate to use an existing did")) ··· 80 authDid, err := s.validateServiceAuth(e.Request().Context(), token, "com.atproto.server.createAccount") 81 82 if err != nil { 83 - s.logger.Warn("error validating authorization token", "endpoint", "com.atproto.server.createAccount", "error", err) 84 return helpers.UnauthorizedError(e, to.StringPtr("invalid authorization token")) 85 } 86 ··· 90 } 91 92 // see if the handle is already taken 93 - actor, err := s.getActorByHandle(request.Handle) 94 if err != nil && err != gorm.ErrRecordNotFound { 95 - s.logger.Error("error looking up handle in db", "endpoint", "com.atproto.server.createAccount", "error", err) 96 return helpers.ServerError(e, nil) 97 } 98 if err == nil && actor.Did != signupDid { ··· 109 return helpers.InputError(e, to.StringPtr("InvalidInviteCode")) 110 } 111 112 - if err := s.db.Raw("SELECT * FROM invite_codes WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil { 113 if err == gorm.ErrRecordNotFound { 114 return helpers.InputError(e, to.StringPtr("InvalidInviteCode")) 115 } 116 - s.logger.Error("error getting invite code from db", "error", err) 117 return helpers.ServerError(e, nil) 118 } 119 ··· 123 } 124 125 // see if the email is already taken 126 - existingRepo, err := s.getRepoByEmail(request.Email) 127 if err != nil && err != gorm.ErrRecordNotFound { 128 - s.logger.Error("error looking up email in db", "endpoint", "com.atproto.server.createAccount", "error", err) 129 return helpers.ServerError(e, nil) 130 } 131 if err == nil && existingRepo.Did != signupDid { ··· 137 var k *atcrypto.PrivateKeyK256 138 139 if signupDid != "" { 140 - reservedKey, err := s.getReservedKey(signupDid) 141 if err != nil { 142 - s.logger.Error("error looking up reserved key", "error", err) 143 } 144 if reservedKey != nil { 145 k, err = atcrypto.ParsePrivateBytesK256(reservedKey.PrivateKey) 146 if err != nil { 147 - s.logger.Error("error parsing reserved key", "error", err) 148 k = nil 149 } else { 150 defer func() { 151 - if delErr := s.deleteReservedKey(reservedKey.KeyDid, reservedKey.Did); delErr != nil { 152 - s.logger.Error("error deleting reserved key", "error", delErr) 153 } 154 }() 155 } ··· 159 if k == nil { 160 k, err = atcrypto.GeneratePrivateKeyK256() 161 if err != nil { 162 - s.logger.Error("error creating signing key", "endpoint", "com.atproto.server.createAccount", "error", err) 163 return helpers.ServerError(e, nil) 164 } 165 } ··· 167 if signupDid == "" { 168 did, op, err := s.plcClient.CreateDID(k, "", request.Handle) 169 if err != nil { 170 - s.logger.Error("error creating operation", "endpoint", "com.atproto.server.createAccount", "error", err) 171 return helpers.ServerError(e, nil) 172 } 173 174 if err := s.plcClient.SendOperation(e.Request().Context(), did, op); err != nil { 175 - s.logger.Error("error sending plc op", "endpoint", "com.atproto.server.createAccount", "error", err) 176 return helpers.ServerError(e, nil) 177 } 178 signupDid = did ··· 180 181 hashed, err := bcrypt.GenerateFromPassword([]byte(request.Password), 10) 182 if err != nil { 183 - s.logger.Error("error hashing password", "error", err) 184 return helpers.ServerError(e, nil) 185 } 186 ··· 199 Handle: request.Handle, 200 } 201 202 - if err := s.db.Create(&urepo, nil).Error; err != nil { 203 - s.logger.Error("error inserting new repo", "error", err) 204 return helpers.ServerError(e, nil) 205 } 206 - 207 - if err := s.db.Create(&actor, nil).Error; err != nil { 208 - s.logger.Error("error inserting new actor", "error", err) 209 return helpers.ServerError(e, nil) 210 } 211 } else { 212 - if err := s.db.Save(&actor, nil).Error; err != nil { 213 - s.logger.Error("error inserting new actor", "error", err) 214 return helpers.ServerError(e, nil) 215 } 216 } ··· 221 222 root, rev, err := r.Commit(context.TODO(), urepo.SignFor) 223 if err != nil { 224 - s.logger.Error("error committing", "error", err) 225 return helpers.ServerError(e, nil) 226 } 227 228 if err := s.UpdateRepo(context.TODO(), urepo.Did, root, rev); err != nil { 229 - s.logger.Error("error updating repo after commit", "error", err) 230 return helpers.ServerError(e, nil) 231 } 232 ··· 241 } 242 243 if s.config.RequireInvite { 244 - 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 { 245 - s.logger.Error("error decrementing use count", "error", err) 246 return helpers.ServerError(e, nil) 247 } 248 } 249 250 - sess, err := s.createSession(&urepo) 251 if err != nil { 252 - s.logger.Error("error creating new session", "error", err) 253 return helpers.ServerError(e, nil) 254 } 255 256 go func() { 257 if err := s.sendEmailVerification(urepo.Email, actor.Handle, *urepo.EmailVerificationCode); err != nil { 258 - s.logger.Error("error sending email verification email", "error", err) 259 } 260 if err := s.sendWelcomeMail(urepo.Email, actor.Handle); err != nil { 261 - s.logger.Error("error sending welcome email", "error", err) 262 } 263 }() 264
··· 36 } 37 38 func (s *Server) handleCreateAccount(e echo.Context) error { 39 + ctx := e.Request().Context() 40 + logger := s.logger.With("name", "handleServerCreateAccount") 41 + 42 var request ComAtprotoServerCreateAccountRequest 43 44 if err := e.Bind(&request); err != nil { 45 + logger.Error("error receiving request", "endpoint", "com.atproto.server.createAccount", "error", err) 46 return helpers.ServerError(e, nil) 47 } 48 49 request.Handle = strings.ToLower(request.Handle) 50 51 if err := e.Validate(request); err != nil { 52 + logger.Error("error validating request", "endpoint", "com.atproto.server.createAccount", "error", err) 53 54 var verr ValidationError 55 if errors.As(err, &verr) { ··· 71 } 72 } 73 } 74 + 75 var signupDid string 76 if request.Did != nil { 77 + signupDid = *request.Did 78 + 79 token := strings.TrimSpace(strings.Replace(e.Request().Header.Get("authorization"), "Bearer ", "", 1)) 80 if token == "" { 81 return helpers.UnauthorizedError(e, to.StringPtr("must authenticate to use an existing did")) ··· 83 authDid, err := s.validateServiceAuth(e.Request().Context(), token, "com.atproto.server.createAccount") 84 85 if err != nil { 86 + logger.Warn("error validating authorization token", "endpoint", "com.atproto.server.createAccount", "error", err) 87 return helpers.UnauthorizedError(e, to.StringPtr("invalid authorization token")) 88 } 89 ··· 93 } 94 95 // see if the handle is already taken 96 + actor, err := s.getActorByHandle(ctx, request.Handle) 97 if err != nil && err != gorm.ErrRecordNotFound { 98 + logger.Error("error looking up handle in db", "endpoint", "com.atproto.server.createAccount", "error", err) 99 return helpers.ServerError(e, nil) 100 } 101 if err == nil && actor.Did != signupDid { ··· 112 return helpers.InputError(e, to.StringPtr("InvalidInviteCode")) 113 } 114 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 ··· 126 } 127 128 // see if the email is already taken 129 + existingRepo, err := s.getRepoByEmail(ctx, request.Email) 130 if err != nil && err != gorm.ErrRecordNotFound { 131 + logger.Error("error looking up email in db", "endpoint", "com.atproto.server.createAccount", "error", err) 132 return helpers.ServerError(e, nil) 133 } 134 if err == nil && existingRepo.Did != signupDid { ··· 140 var k *atcrypto.PrivateKeyK256 141 142 if signupDid != "" { 143 + reservedKey, err := s.getReservedKey(ctx, signupDid) 144 if err != nil { 145 + logger.Error("error looking up reserved key", "error", err) 146 } 147 if reservedKey != nil { 148 k, err = atcrypto.ParsePrivateBytesK256(reservedKey.PrivateKey) 149 if err != nil { 150 + logger.Error("error parsing reserved key", "error", err) 151 k = nil 152 } else { 153 defer func() { 154 + if delErr := s.deleteReservedKey(ctx, reservedKey.KeyDid, reservedKey.Did); delErr != nil { 155 + logger.Error("error deleting reserved key", "error", delErr) 156 } 157 }() 158 } ··· 162 if k == nil { 163 k, err = atcrypto.GeneratePrivateKeyK256() 164 if err != nil { 165 + logger.Error("error creating signing key", "endpoint", "com.atproto.server.createAccount", "error", err) 166 return helpers.ServerError(e, nil) 167 } 168 } ··· 170 if signupDid == "" { 171 did, op, err := s.plcClient.CreateDID(k, "", request.Handle) 172 if err != nil { 173 + logger.Error("error creating operation", "endpoint", "com.atproto.server.createAccount", "error", err) 174 return helpers.ServerError(e, nil) 175 } 176 177 if err := s.plcClient.SendOperation(e.Request().Context(), did, op); err != nil { 178 + logger.Error("error sending plc op", "endpoint", "com.atproto.server.createAccount", "error", err) 179 return helpers.ServerError(e, nil) 180 } 181 signupDid = did ··· 183 184 hashed, err := bcrypt.GenerateFromPassword([]byte(request.Password), 10) 185 if err != nil { 186 + logger.Error("error hashing password", "error", err) 187 return helpers.ServerError(e, nil) 188 } 189 ··· 202 Handle: request.Handle, 203 } 204 205 + if err := s.db.Create(ctx, &urepo, nil).Error; err != nil { 206 + logger.Error("error inserting new repo", "error", err) 207 return helpers.ServerError(e, nil) 208 } 209 + 210 + if err := s.db.Create(ctx, &actor, nil).Error; err != nil { 211 + logger.Error("error inserting new actor", "error", err) 212 return helpers.ServerError(e, nil) 213 } 214 } else { 215 + if err := s.db.Save(ctx, &actor, nil).Error; err != nil { 216 + logger.Error("error inserting new actor", "error", err) 217 return helpers.ServerError(e, nil) 218 } 219 } ··· 224 225 root, rev, err := r.Commit(context.TODO(), urepo.SignFor) 226 if err != nil { 227 + logger.Error("error committing", "error", err) 228 return helpers.ServerError(e, nil) 229 } 230 231 if err := s.UpdateRepo(context.TODO(), urepo.Did, root, rev); err != nil { 232 + logger.Error("error updating repo after commit", "error", err) 233 return helpers.ServerError(e, nil) 234 } 235 ··· 244 } 245 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 } 251 } 252 253 + sess, err := s.createSession(ctx, &urepo) 254 if err != nil { 255 + logger.Error("error creating new session", "error", err) 256 return helpers.ServerError(e, nil) 257 } 258 259 go func() { 260 if err := s.sendEmailVerification(urepo.Email, actor.Handle, *urepo.EmailVerificationCode); err != nil { 261 + logger.Error("error sending email verification email", "error", err) 262 } 263 if err := s.sendWelcomeMail(urepo.Email, actor.Handle); err != nil { 264 + logger.Error("error sending welcome email", "error", err) 265 } 266 }() 267
+7 -4
server/handle_server_create_invite_code.go
··· 17 } 18 19 func (s *Server) handleCreateInviteCode(e echo.Context) error { 20 var req ComAtprotoServerCreateInviteCodeRequest 21 if err := e.Bind(&req); err != nil { 22 - s.logger.Error("error binding", "error", err) 23 return helpers.ServerError(e, nil) 24 } 25 26 if err := e.Validate(req); err != nil { 27 - s.logger.Error("error validating", "error", err) 28 return helpers.InputError(e, nil) 29 } 30 ··· 37 acc = *req.ForAccount 38 } 39 40 - if err := s.db.Create(&models.InviteCode{ 41 Code: ic, 42 Did: acc, 43 RemainingUseCount: req.UseCount, 44 }, nil).Error; err != nil { 45 - s.logger.Error("error creating invite code", "error", err) 46 return helpers.ServerError(e, nil) 47 } 48
··· 17 } 18 19 func (s *Server) handleCreateInviteCode(e echo.Context) error { 20 + ctx := e.Request().Context() 21 + logger := s.logger.With("name", "handleServerCreateInviteCode") 22 + 23 var req ComAtprotoServerCreateInviteCodeRequest 24 if err := e.Bind(&req); err != nil { 25 + logger.Error("error binding", "error", err) 26 return helpers.ServerError(e, nil) 27 } 28 29 if err := e.Validate(req); err != nil { 30 + logger.Error("error validating", "error", err) 31 return helpers.InputError(e, nil) 32 } 33 ··· 40 acc = *req.ForAccount 41 } 42 43 + if err := s.db.Create(ctx, &models.InviteCode{ 44 Code: ic, 45 Did: acc, 46 RemainingUseCount: req.UseCount, 47 }, nil).Error; err != nil { 48 + logger.Error("error creating invite code", "error", err) 49 return helpers.ServerError(e, nil) 50 } 51
+7 -4
server/handle_server_create_invite_codes.go
··· 22 } 23 24 func (s *Server) handleCreateInviteCodes(e echo.Context) error { 25 var req ComAtprotoServerCreateInviteCodesRequest 26 if err := e.Bind(&req); err != nil { 27 - s.logger.Error("error binding", "error", err) 28 return helpers.ServerError(e, nil) 29 } 30 31 if err := e.Validate(req); err != nil { 32 - s.logger.Error("error validating", "error", err) 33 return helpers.InputError(e, nil) 34 } 35 ··· 50 ic := uuid.NewString() 51 ics = append(ics, ic) 52 53 - if err := s.db.Create(&models.InviteCode{ 54 Code: ic, 55 Did: did, 56 RemainingUseCount: req.UseCount, 57 }, nil).Error; err != nil { 58 - s.logger.Error("error creating invite code", "error", err) 59 return helpers.ServerError(e, nil) 60 } 61 }
··· 22 } 23 24 func (s *Server) handleCreateInviteCodes(e echo.Context) error { 25 + ctx := e.Request().Context() 26 + logger := s.logger.With("name", "handleServerCreateInviteCodes") 27 + 28 var req ComAtprotoServerCreateInviteCodesRequest 29 if err := e.Bind(&req); err != nil { 30 + logger.Error("error binding", "error", err) 31 return helpers.ServerError(e, nil) 32 } 33 34 if err := e.Validate(req); err != nil { 35 + logger.Error("error validating", "error", err) 36 return helpers.InputError(e, nil) 37 } 38 ··· 53 ic := uuid.NewString() 54 ics = append(ics, ic) 55 56 + if err := s.db.Create(ctx, &models.InviteCode{ 57 Code: ic, 58 Did: did, 59 RemainingUseCount: req.UseCount, 60 }, nil).Error; err != nil { 61 + logger.Error("error creating invite code", "error", err) 62 return helpers.ServerError(e, nil) 63 } 64 }
+65 -9
server/handle_server_create_session.go
··· 1 package server 2 3 import ( 4 "errors" 5 "strings" 6 7 "github.com/Azure/go-autorest/autorest/to" 8 "github.com/bluesky-social/indigo/atproto/syntax" ··· 32 } 33 34 func (s *Server) handleCreateSession(e echo.Context) error { 35 var req ComAtprotoServerCreateSessionRequest 36 if err := e.Bind(&req); err != nil { 37 - s.logger.Error("error binding request", "endpoint", "com.atproto.server.serverCreateSession", "error", err) 38 return helpers.ServerError(e, nil) 39 } 40 ··· 65 var err error 66 switch idtype { 67 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 69 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 71 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 73 } 74 75 if err != nil { ··· 77 return helpers.InputError(e, to.StringPtr("InvalidRequest")) 78 } 79 80 - s.logger.Error("erorr looking up repo", "endpoint", "com.atproto.server.createSession", "error", err) 81 return helpers.ServerError(e, nil) 82 } 83 84 if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil { 85 if err != bcrypt.ErrMismatchedHashAndPassword { 86 - s.logger.Error("erorr comparing hash and password", "error", err) 87 } 88 return helpers.InputError(e, to.StringPtr("InvalidRequest")) 89 } 90 91 - sess, err := s.createSession(&repo.Repo) 92 if err != nil { 93 - s.logger.Error("error creating session", "error", err) 94 return helpers.ServerError(e, nil) 95 } 96 ··· 101 Did: repo.Repo.Did, 102 Email: repo.Email, 103 EmailConfirmed: repo.EmailConfirmedAt != nil, 104 - EmailAuthFactor: false, 105 Active: repo.Active(), 106 Status: repo.Status(), 107 }) 108 }
··· 1 package server 2 3 import ( 4 + "context" 5 "errors" 6 + "fmt" 7 "strings" 8 + "time" 9 10 "github.com/Azure/go-autorest/autorest/to" 11 "github.com/bluesky-social/indigo/atproto/syntax" ··· 35 } 36 37 func (s *Server) handleCreateSession(e echo.Context) error { 38 + ctx := e.Request().Context() 39 + logger := s.logger.With("name", "handleServerCreateSession") 40 + 41 var req ComAtprotoServerCreateSessionRequest 42 if err := e.Bind(&req); err != nil { 43 + logger.Error("error binding request", "endpoint", "com.atproto.server.serverCreateSession", "error", err) 44 return helpers.ServerError(e, nil) 45 } 46 ··· 71 var err error 72 switch idtype { 73 case "did": 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 75 case "handle": 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 77 case "email": 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 79 } 80 81 if err != nil { ··· 83 return helpers.InputError(e, to.StringPtr("InvalidRequest")) 84 } 85 86 + logger.Error("erorr looking up repo", "endpoint", "com.atproto.server.createSession", "error", err) 87 return helpers.ServerError(e, nil) 88 } 89 90 if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil { 91 if err != bcrypt.ErrMismatchedHashAndPassword { 92 + logger.Error("erorr comparing hash and password", "error", err) 93 } 94 return helpers.InputError(e, to.StringPtr("InvalidRequest")) 95 } 96 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) 130 if err != nil { 131 + logger.Error("error creating session", "error", err) 132 return helpers.ServerError(e, nil) 133 } 134 ··· 139 Did: repo.Repo.Did, 140 Email: repo.Email, 141 EmailConfirmed: repo.EmailConfirmedAt != nil, 142 + EmailAuthFactor: repo.TwoFactorType != models.TwoFactorTypeNone, 143 Active: repo.Active(), 144 Status: repo.Status(), 145 }) 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 } 20 21 func (s *Server) handleServerDeactivateAccount(e echo.Context) error { 22 var req ComAtprotoServerDeactivateAccountRequest 23 if err := e.Bind(&req); err != nil { 24 - s.logger.Error("error binding", "error", err) 25 return helpers.ServerError(e, nil) 26 } 27 28 urepo := e.Get("repo").(*models.RepoActor) 29 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) 32 return helpers.ServerError(e, nil) 33 } 34
··· 19 } 20 21 func (s *Server) handleServerDeactivateAccount(e echo.Context) error { 22 + ctx := e.Request().Context() 23 + logger := s.logger.With("name", "handleServerDeactivateAccount") 24 + 25 var req ComAtprotoServerDeactivateAccountRequest 26 if err := e.Bind(&req); err != nil { 27 + logger.Error("error binding", "error", err) 28 return helpers.ServerError(e, nil) 29 } 30 31 urepo := e.Get("repo").(*models.RepoActor) 32 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) 35 return helpers.ServerError(e, nil) 36 } 37
+34 -29
server/handle_server_delete_account.go
··· 20 } 21 22 func (s *Server) handleServerDeleteAccount(e echo.Context) error { 23 var req ComAtprotoServerDeleteAccountRequest 24 if err := e.Bind(&req); err != nil { 25 - s.logger.Error("error binding", "error", err) 26 return helpers.ServerError(e, nil) 27 } 28 29 if err := e.Validate(&req); err != nil { 30 - s.logger.Error("error validating", "error", err) 31 return helpers.ServerError(e, nil) 32 } 33 34 - urepo, err := s.getRepoActorByDid(req.Did) 35 if err != nil { 36 - s.logger.Error("error getting repo", "error", err) 37 return echo.NewHTTPError(400, "account not found") 38 } 39 40 if err := bcrypt.CompareHashAndPassword([]byte(urepo.Repo.Password), []byte(req.Password)); err != nil { 41 - s.logger.Error("password mismatch", "error", err) 42 return echo.NewHTTPError(401, "Invalid did or password") 43 } 44 45 if urepo.Repo.AccountDeleteCode == nil || urepo.Repo.AccountDeleteCodeExpiresAt == nil { 46 - s.logger.Error("no deletion token found for account") 47 return echo.NewHTTPError(400, map[string]interface{}{ 48 "error": "InvalidToken", 49 "message": "Token is invalid", ··· 51 } 52 53 if *urepo.Repo.AccountDeleteCode != req.Token { 54 - s.logger.Error("deletion token mismatch") 55 return echo.NewHTTPError(400, map[string]interface{}{ 56 "error": "InvalidToken", 57 "message": "Token is invalid", ··· 59 } 60 61 if time.Now().UTC().After(*urepo.Repo.AccountDeleteCodeExpiresAt) { 62 - s.logger.Error("deletion token expired") 63 return echo.NewHTTPError(400, map[string]interface{}{ 64 "error": "ExpiredToken", 65 "message": "Token is expired", 66 }) 67 } 68 69 - tx := s.db.BeginDangerously() 70 if tx.Error != nil { 71 - s.logger.Error("error starting transaction", "error", tx.Error) 72 return helpers.ServerError(e, nil) 73 } 74 75 if err := tx.Exec("DELETE FROM blocks WHERE did = ?", nil, req.Did).Error; err != nil { 76 - tx.Rollback() 77 - s.logger.Error("error deleting blocks", "error", err) 78 return helpers.ServerError(e, nil) 79 } 80 81 if err := tx.Exec("DELETE FROM records WHERE did = ?", nil, req.Did).Error; err != nil { 82 - tx.Rollback() 83 - s.logger.Error("error deleting records", "error", err) 84 return helpers.ServerError(e, nil) 85 } 86 87 if err := tx.Exec("DELETE FROM blobs WHERE did = ?", nil, req.Did).Error; err != nil { 88 - tx.Rollback() 89 - s.logger.Error("error deleting blobs", "error", err) 90 return helpers.ServerError(e, nil) 91 } 92 93 if err := tx.Exec("DELETE FROM tokens WHERE did = ?", nil, req.Did).Error; err != nil { 94 - tx.Rollback() 95 - s.logger.Error("error deleting tokens", "error", err) 96 return helpers.ServerError(e, nil) 97 } 98 99 if err := tx.Exec("DELETE FROM refresh_tokens WHERE did = ?", nil, req.Did).Error; err != nil { 100 - tx.Rollback() 101 - s.logger.Error("error deleting refresh tokens", "error", err) 102 return helpers.ServerError(e, nil) 103 } 104 105 if err := tx.Exec("DELETE FROM reserved_keys WHERE did = ?", nil, req.Did).Error; err != nil { 106 - tx.Rollback() 107 - s.logger.Error("error deleting reserved keys", "error", err) 108 return helpers.ServerError(e, nil) 109 } 110 111 if err := tx.Exec("DELETE FROM invite_codes WHERE did = ?", nil, req.Did).Error; err != nil { 112 - tx.Rollback() 113 - s.logger.Error("error deleting invite codes", "error", err) 114 return helpers.ServerError(e, nil) 115 } 116 117 if err := tx.Exec("DELETE FROM actors WHERE did = ?", nil, req.Did).Error; err != nil { 118 - tx.Rollback() 119 - s.logger.Error("error deleting actor", "error", err) 120 return helpers.ServerError(e, nil) 121 } 122 123 if err := tx.Exec("DELETE FROM repos WHERE did = ?", nil, req.Did).Error; err != nil { 124 - tx.Rollback() 125 - s.logger.Error("error deleting repo", "error", err) 126 return helpers.ServerError(e, nil) 127 } 128 129 if err := tx.Commit().Error; err != nil { 130 - s.logger.Error("error committing transaction", "error", err) 131 return helpers.ServerError(e, nil) 132 } 133
··· 20 } 21 22 func (s *Server) handleServerDeleteAccount(e echo.Context) error { 23 + ctx := e.Request().Context() 24 + logger := s.logger.With("name", "handleServerDeleteAccount") 25 + 26 var req ComAtprotoServerDeleteAccountRequest 27 if err := e.Bind(&req); err != nil { 28 + logger.Error("error binding", "error", err) 29 return helpers.ServerError(e, nil) 30 } 31 32 if err := e.Validate(&req); err != nil { 33 + logger.Error("error validating", "error", err) 34 return helpers.ServerError(e, nil) 35 } 36 37 + urepo, err := s.getRepoActorByDid(ctx, req.Did) 38 if err != nil { 39 + logger.Error("error getting repo", "error", err) 40 return echo.NewHTTPError(400, "account not found") 41 } 42 43 if err := bcrypt.CompareHashAndPassword([]byte(urepo.Repo.Password), []byte(req.Password)); err != nil { 44 + logger.Error("password mismatch", "error", err) 45 return echo.NewHTTPError(401, "Invalid did or password") 46 } 47 48 if urepo.Repo.AccountDeleteCode == nil || urepo.Repo.AccountDeleteCodeExpiresAt == nil { 49 + logger.Error("no deletion token found for account") 50 return echo.NewHTTPError(400, map[string]interface{}{ 51 "error": "InvalidToken", 52 "message": "Token is invalid", ··· 54 } 55 56 if *urepo.Repo.AccountDeleteCode != req.Token { 57 + logger.Error("deletion token mismatch") 58 return echo.NewHTTPError(400, map[string]interface{}{ 59 "error": "InvalidToken", 60 "message": "Token is invalid", ··· 62 } 63 64 if time.Now().UTC().After(*urepo.Repo.AccountDeleteCodeExpiresAt) { 65 + logger.Error("deletion token expired") 66 return echo.NewHTTPError(400, map[string]interface{}{ 67 "error": "ExpiredToken", 68 "message": "Token is expired", 69 }) 70 } 71 72 + tx := s.db.BeginDangerously(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) 94 return helpers.ServerError(e, nil) 95 } 96 97 if err := tx.Exec("DELETE FROM blobs WHERE did = ?", nil, req.Did).Error; err != nil { 98 + logger.Error("error deleting blobs", "error", err) 99 return helpers.ServerError(e, nil) 100 } 101 102 if err := tx.Exec("DELETE FROM tokens WHERE did = ?", nil, req.Did).Error; err != nil { 103 + logger.Error("error deleting tokens", "error", err) 104 return helpers.ServerError(e, nil) 105 } 106 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) 109 return helpers.ServerError(e, nil) 110 } 111 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) 114 return helpers.ServerError(e, nil) 115 } 116 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) 119 return helpers.ServerError(e, nil) 120 } 121 122 if err := tx.Exec("DELETE FROM actors WHERE did = ?", nil, req.Did).Error; err != nil { 123 + logger.Error("error deleting actor", "error", err) 124 return helpers.ServerError(e, nil) 125 } 126 127 if err := tx.Exec("DELETE FROM repos WHERE did = ?", nil, req.Did).Error; err != nil { 128 + logger.Error("error deleting repo", "error", err) 129 return helpers.ServerError(e, nil) 130 } 131 132 + status = "ok" 133 + 134 if err := tx.Commit().Error; err != nil { 135 + logger.Error("error committing transaction", "error", err) 136 return helpers.ServerError(e, nil) 137 } 138
+4 -2
server/handle_server_delete_session.go
··· 7 ) 8 9 func (s *Server) handleDeleteSession(e echo.Context) error { 10 token := e.Get("token").(string) 11 12 var acctok models.Token 13 - if err := s.db.Raw("DELETE FROM tokens WHERE token = ? RETURNING *", nil, token).Scan(&acctok).Error; err != nil { 14 s.logger.Error("error deleting access token from db", "error", err) 15 return helpers.ServerError(e, nil) 16 } 17 18 - if err := s.db.Exec("DELETE FROM refresh_tokens WHERE token = ?", nil, acctok.RefreshToken).Error; err != nil { 19 s.logger.Error("error deleting refresh token from db", "error", err) 20 return helpers.ServerError(e, nil) 21 }
··· 7 ) 8 9 func (s *Server) handleDeleteSession(e echo.Context) error { 10 + ctx := e.Request().Context() 11 + 12 token := e.Get("token").(string) 13 14 var acctok models.Token 15 + if err := s.db.Raw(ctx, "DELETE FROM tokens WHERE token = ? RETURNING *", nil, token).Scan(&acctok).Error; err != nil { 16 s.logger.Error("error deleting access token from db", "error", err) 17 return helpers.ServerError(e, nil) 18 } 19 20 + if err := s.db.Exec(ctx, "DELETE FROM refresh_tokens WHERE token = ?", nil, acctok.RefreshToken).Error; err != nil { 21 s.logger.Error("error deleting refresh token from db", "error", err) 22 return helpers.ServerError(e, nil) 23 }
+7 -5
server/handle_server_get_service_auth.go
··· 25 } 26 27 func (s *Server) handleServerGetServiceAuth(e echo.Context) error { 28 var req ServerGetServiceAuthRequest 29 if err := e.Bind(&req); err != nil { 30 - s.logger.Error("could not bind service auth request", "error", err) 31 return helpers.ServerError(e, nil) 32 } 33 ··· 64 } 65 hj, err := json.Marshal(header) 66 if err != nil { 67 - s.logger.Error("error marshaling header", "error", err) 68 return helpers.ServerError(e, nil) 69 } 70 ··· 82 } 83 pj, err := json.Marshal(payload) 84 if err != nil { 85 - s.logger.Error("error marashaling payload", "error", err) 86 return helpers.ServerError(e, nil) 87 } 88 ··· 93 94 sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey) 95 if err != nil { 96 - s.logger.Error("can't load private key", "error", err) 97 return err 98 } 99 100 R, S, _, err := sk.SignRaw(rand.Reader, hash[:]) 101 if err != nil { 102 - s.logger.Error("error signing", "error", err) 103 return helpers.ServerError(e, nil) 104 } 105
··· 25 } 26 27 func (s *Server) handleServerGetServiceAuth(e echo.Context) error { 28 + logger := s.logger.With("name", "handleServerGetServiceAuth") 29 + 30 var req ServerGetServiceAuthRequest 31 if err := e.Bind(&req); err != nil { 32 + logger.Error("could not bind service auth request", "error", err) 33 return helpers.ServerError(e, nil) 34 } 35 ··· 66 } 67 hj, err := json.Marshal(header) 68 if err != nil { 69 + logger.Error("error marshaling header", "error", err) 70 return helpers.ServerError(e, nil) 71 } 72 ··· 84 } 85 pj, err := json.Marshal(payload) 86 if err != nil { 87 + logger.Error("error marashaling payload", "error", err) 88 return helpers.ServerError(e, nil) 89 } 90 ··· 95 96 sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey) 97 if err != nil { 98 + logger.Error("can't load private key", "error", err) 99 return err 100 } 101 102 R, S, _, err := sk.SignRaw(rand.Reader, hash[:]) 103 if err != nil { 104 + logger.Error("error signing", "error", err) 105 return helpers.ServerError(e, nil) 106 } 107
+1 -1
server/handle_server_get_session.go
··· 23 Did: repo.Repo.Did, 24 Email: repo.Email, 25 EmailConfirmed: repo.EmailConfirmedAt != nil, 26 - EmailAuthFactor: false, // TODO: todo todo 27 Active: repo.Active(), 28 Status: repo.Status(), 29 })
··· 23 Did: repo.Repo.Did, 24 Email: repo.Email, 25 EmailConfirmed: repo.EmailConfirmedAt != nil, 26 + EmailAuthFactor: repo.TwoFactorType != models.TwoFactorTypeNone, 27 Active: repo.Active(), 28 Status: repo.Status(), 29 })
+9 -6
server/handle_server_refresh_session.go
··· 16 } 17 18 func (s *Server) handleRefreshSession(e echo.Context) error { 19 token := e.Get("token").(string) 20 repo := e.Get("repo").(*models.RepoActor) 21 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) 24 return helpers.ServerError(e, nil) 25 } 26 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) 29 return helpers.ServerError(e, nil) 30 } 31 32 - sess, err := s.createSession(&repo.Repo) 33 if err != nil { 34 - s.logger.Error("error creating new session for refresh", "error", err) 35 return helpers.ServerError(e, nil) 36 } 37
··· 16 } 17 18 func (s *Server) handleRefreshSession(e echo.Context) error { 19 + ctx := e.Request().Context() 20 + logger := s.logger.With("name", "handleServerRefreshSession") 21 + 22 token := e.Get("token").(string) 23 repo := e.Get("repo").(*models.RepoActor) 24 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) 27 return helpers.ServerError(e, nil) 28 } 29 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) 32 return helpers.ServerError(e, nil) 33 } 34 35 + sess, err := s.createSession(ctx, &repo.Repo) 36 if err != nil { 37 + logger.Error("error creating new session for refresh", "error", err) 38 return helpers.ServerError(e, nil) 39 } 40
+6 -3
server/handle_server_request_account_delete.go
··· 10 ) 11 12 func (s *Server) handleServerRequestAccountDelete(e echo.Context) error { 13 urepo := e.Get("repo").(*models.RepoActor) 14 15 token := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 16 expiresAt := time.Now().UTC().Add(15 * time.Minute) 17 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) 20 return helpers.ServerError(e, nil) 21 } 22 23 if urepo.Email != "" { 24 if err := s.sendAccountDeleteEmail(urepo.Email, urepo.Actor.Handle, token); err != nil { 25 - s.logger.Error("error sending account deletion email", "error", err) 26 } 27 } 28
··· 10 ) 11 12 func (s *Server) handleServerRequestAccountDelete(e echo.Context) error { 13 + ctx := e.Request().Context() 14 + logger := s.logger.With("name", "handleServerRequestAccountDelete") 15 + 16 urepo := e.Get("repo").(*models.RepoActor) 17 18 token := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 19 expiresAt := time.Now().UTC().Add(15 * time.Minute) 20 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) 23 return helpers.ServerError(e, nil) 24 } 25 26 if urepo.Email != "" { 27 if err := s.sendAccountDeleteEmail(urepo.Email, urepo.Actor.Handle, token); err != nil { 28 + logger.Error("error sending account deletion email", "error", err) 29 } 30 } 31
+6 -3
server/handle_server_request_email_confirmation.go
··· 11 ) 12 13 func (s *Server) handleServerRequestEmailConfirmation(e echo.Context) error { 14 urepo := e.Get("repo").(*models.RepoActor) 15 16 if urepo.EmailConfirmedAt != nil { ··· 20 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 21 eat := time.Now().Add(10 * time.Minute).UTC() 22 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) 25 return helpers.ServerError(e, nil) 26 } 27 28 if err := s.sendEmailVerification(urepo.Email, urepo.Handle, code); err != nil { 29 - s.logger.Error("error sending mail", "error", err) 30 return helpers.ServerError(e, nil) 31 } 32
··· 11 ) 12 13 func (s *Server) handleServerRequestEmailConfirmation(e echo.Context) error { 14 + ctx := e.Request().Context() 15 + logger := s.logger.With("name", "handleServerRequestEmailConfirm") 16 + 17 urepo := e.Get("repo").(*models.RepoActor) 18 19 if urepo.EmailConfirmedAt != nil { ··· 23 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 24 eat := time.Now().Add(10 * time.Minute).UTC() 25 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) 28 return helpers.ServerError(e, nil) 29 } 30 31 if err := s.sendEmailVerification(urepo.Email, urepo.Handle, code); err != nil { 32 + logger.Error("error sending mail", "error", err) 33 return helpers.ServerError(e, nil) 34 } 35
+6 -3
server/handle_server_request_email_update.go
··· 14 } 15 16 func (s *Server) handleServerRequestEmailUpdate(e echo.Context) error { 17 urepo := e.Get("repo").(*models.RepoActor) 18 19 if urepo.EmailConfirmedAt != nil { 20 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 21 eat := time.Now().Add(10 * time.Minute).UTC() 22 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) 25 return helpers.ServerError(e, nil) 26 } 27 28 if err := s.sendEmailUpdate(urepo.Email, urepo.Handle, code); err != nil { 29 - s.logger.Error("error sending email", "error", err) 30 return helpers.ServerError(e, nil) 31 } 32 }
··· 14 } 15 16 func (s *Server) handleServerRequestEmailUpdate(e echo.Context) error { 17 + ctx := e.Request().Context() 18 + logger := s.logger.With("name", "handleServerRequestEmailUpdate") 19 + 20 urepo := e.Get("repo").(*models.RepoActor) 21 22 if urepo.EmailConfirmedAt != nil { 23 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 24 eat := time.Now().Add(10 * time.Minute).UTC() 25 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) 28 return helpers.ServerError(e, nil) 29 } 30 31 if err := s.sendEmailUpdate(urepo.Email, urepo.Handle, code); err != nil { 32 + logger.Error("error sending email", "error", err) 33 return helpers.ServerError(e, nil) 34 } 35 }
+7 -4
server/handle_server_request_password_reset.go
··· 14 } 15 16 func (s *Server) handleServerRequestPasswordReset(e echo.Context) error { 17 urepo, ok := e.Get("repo").(*models.RepoActor) 18 if !ok { 19 var req ComAtprotoServerRequestPasswordResetRequest ··· 25 return err 26 } 27 28 - murepo, err := s.getRepoActorByEmail(req.Email) 29 if err != nil { 30 return err 31 } ··· 36 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 37 eat := time.Now().Add(10 * time.Minute).UTC() 38 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) 41 return helpers.ServerError(e, nil) 42 } 43 44 if err := s.sendPasswordReset(urepo.Email, urepo.Handle, code); err != nil { 45 - s.logger.Error("error sending email", "error", err) 46 return helpers.ServerError(e, nil) 47 } 48
··· 14 } 15 16 func (s *Server) handleServerRequestPasswordReset(e echo.Context) error { 17 + ctx := e.Request().Context() 18 + logger := s.logger.With("name", "handleServerRequestPasswordReset") 19 + 20 urepo, ok := e.Get("repo").(*models.RepoActor) 21 if !ok { 22 var req ComAtprotoServerRequestPasswordResetRequest ··· 28 return err 29 } 30 31 + murepo, err := s.getRepoActorByEmail(ctx, req.Email) 32 if err != nil { 33 return err 34 } ··· 39 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 40 eat := time.Now().Add(10 * time.Minute).UTC() 41 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) 44 return helpers.ServerError(e, nil) 45 } 46 47 if err := s.sendPasswordReset(urepo.Email, urepo.Handle, code); err != nil { 48 + logger.Error("error sending email", "error", err) 49 return helpers.ServerError(e, nil) 50 } 51
+17 -13
server/handle_server_reserve_signing_key.go
··· 1 package server 2 3 import ( 4 "time" 5 6 "github.com/bluesky-social/indigo/atproto/atcrypto" ··· 18 } 19 20 func (s *Server) handleServerReserveSigningKey(e echo.Context) error { 21 var req ServerReserveSigningKeyRequest 22 if err := e.Bind(&req); err != nil { 23 - s.logger.Error("could not bind reserve signing key request", "error", err) 24 return helpers.ServerError(e, nil) 25 } 26 27 if req.Did != nil && *req.Did != "" { 28 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 != "" { 30 return e.JSON(200, ServerReserveSigningKeyResponse{ 31 SigningKey: existing.KeyDid, 32 }) ··· 35 36 k, err := atcrypto.GeneratePrivateKeyK256() 37 if err != nil { 38 - s.logger.Error("error creating signing key", "endpoint", "com.atproto.server.reserveSigningKey", "error", err) 39 return helpers.ServerError(e, nil) 40 } 41 42 pubKey, err := k.PublicKey() 43 if err != nil { 44 - s.logger.Error("error getting public key", "endpoint", "com.atproto.server.reserveSigningKey", "error", err) 45 return helpers.ServerError(e, nil) 46 } 47 ··· 54 CreatedAt: time.Now(), 55 } 56 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) 59 return helpers.ServerError(e, nil) 60 } 61 62 - s.logger.Info("reserved signing key", "keyDid", keyDid, "forDid", req.Did) 63 64 return e.JSON(200, ServerReserveSigningKeyResponse{ 65 SigningKey: keyDid, 66 }) 67 } 68 69 - func (s *Server) getReservedKey(keyDidOrDid string) (*models.ReservedKey, error) { 70 var reservedKey models.ReservedKey 71 72 - if err := s.db.Raw("SELECT * FROM reserved_keys WHERE key_did = ?", nil, keyDidOrDid).Scan(&reservedKey).Error; err == nil && reservedKey.KeyDid != "" { 73 return &reservedKey, nil 74 } 75 76 - if err := s.db.Raw("SELECT * FROM reserved_keys WHERE did = ?", nil, keyDidOrDid).Scan(&reservedKey).Error; err == nil && reservedKey.KeyDid != "" { 77 return &reservedKey, nil 78 } 79 80 return nil, nil 81 } 82 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 { 85 return err 86 } 87 88 if did != nil && *did != "" { 89 - if err := s.db.Exec("DELETE FROM reserved_keys WHERE did = ?", nil, *did).Error; err != nil { 90 return err 91 } 92 }
··· 1 package server 2 3 import ( 4 + "context" 5 "time" 6 7 "github.com/bluesky-social/indigo/atproto/atcrypto" ··· 19 } 20 21 func (s *Server) handleServerReserveSigningKey(e echo.Context) error { 22 + ctx := e.Request().Context() 23 + logger := s.logger.With("name", "handleServerReserveSigningKey") 24 + 25 var req ServerReserveSigningKeyRequest 26 if err := e.Bind(&req); err != nil { 27 + logger.Error("could not bind reserve signing key request", "error", err) 28 return helpers.ServerError(e, nil) 29 } 30 31 if req.Did != nil && *req.Did != "" { 32 var existing models.ReservedKey 33 + if err := s.db.Raw(ctx, "SELECT * FROM reserved_keys WHERE did = ?", nil, *req.Did).Scan(&existing).Error; err == nil && existing.KeyDid != "" { 34 return e.JSON(200, ServerReserveSigningKeyResponse{ 35 SigningKey: existing.KeyDid, 36 }) ··· 39 40 k, err := atcrypto.GeneratePrivateKeyK256() 41 if err != nil { 42 + logger.Error("error creating signing key", "endpoint", "com.atproto.server.reserveSigningKey", "error", err) 43 return helpers.ServerError(e, nil) 44 } 45 46 pubKey, err := k.PublicKey() 47 if err != nil { 48 + logger.Error("error getting public key", "endpoint", "com.atproto.server.reserveSigningKey", "error", err) 49 return helpers.ServerError(e, nil) 50 } 51 ··· 58 CreatedAt: time.Now(), 59 } 60 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) 63 return helpers.ServerError(e, nil) 64 } 65 66 + logger.Info("reserved signing key", "keyDid", keyDid, "forDid", req.Did) 67 68 return e.JSON(200, ServerReserveSigningKeyResponse{ 69 SigningKey: keyDid, 70 }) 71 } 72 73 + func (s *Server) getReservedKey(ctx context.Context, keyDidOrDid string) (*models.ReservedKey, error) { 74 var reservedKey models.ReservedKey 75 76 + if err := s.db.Raw(ctx, "SELECT * FROM reserved_keys WHERE key_did = ?", nil, keyDidOrDid).Scan(&reservedKey).Error; err == nil && reservedKey.KeyDid != "" { 77 return &reservedKey, nil 78 } 79 80 + if err := s.db.Raw(ctx, "SELECT * FROM reserved_keys WHERE did = ?", nil, keyDidOrDid).Scan(&reservedKey).Error; err == nil && reservedKey.KeyDid != "" { 81 return &reservedKey, nil 82 } 83 84 return nil, nil 85 } 86 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 { 89 return err 90 } 91 92 if did != nil && *did != "" { 93 + if err := s.db.Exec(ctx, "DELETE FROM reserved_keys WHERE did = ?", nil, *did).Error; err != nil { 94 return err 95 } 96 }
+7 -4
server/handle_server_reset_password.go
··· 16 } 17 18 func (s *Server) handleServerResetPassword(e echo.Context) error { 19 urepo := e.Get("repo").(*models.RepoActor) 20 21 var req ComAtprotoServerResetPasswordRequest 22 if err := e.Bind(&req); err != nil { 23 - s.logger.Error("error binding", "error", err) 24 return helpers.ServerError(e, nil) 25 } 26 ··· 42 43 hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10) 44 if err != nil { 45 - s.logger.Error("error creating hash", "error", err) 46 return helpers.ServerError(e, nil) 47 } 48 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) 51 return helpers.ServerError(e, nil) 52 } 53
··· 16 } 17 18 func (s *Server) handleServerResetPassword(e echo.Context) error { 19 + ctx := e.Request().Context() 20 + logger := s.logger.With("name", "handleServerResetPassword") 21 + 22 urepo := e.Get("repo").(*models.RepoActor) 23 24 var req ComAtprotoServerResetPasswordRequest 25 if err := e.Bind(&req); err != nil { 26 + logger.Error("error binding", "error", err) 27 return helpers.ServerError(e, nil) 28 } 29 ··· 45 46 hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10) 47 if err != nil { 48 + logger.Error("error creating hash", "error", err) 49 return helpers.ServerError(e, nil) 50 } 51 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) 54 return helpers.ServerError(e, nil) 55 } 56
+3 -1
server/handle_server_resolve_handle.go
··· 10 ) 11 12 func (s *Server) handleResolveHandle(e echo.Context) error { 13 type Resp struct { 14 Did string `json:"did"` 15 } ··· 28 ctx := context.WithValue(e.Request().Context(), "skip-cache", true) 29 did, err := s.passport.ResolveHandle(ctx, parsed.String()) 30 if err != nil { 31 - s.logger.Error("error resolving handle", "error", err) 32 return helpers.ServerError(e, nil) 33 } 34
··· 10 ) 11 12 func (s *Server) handleResolveHandle(e echo.Context) error { 13 + logger := s.logger.With("name", "handleServerResolveHandle") 14 + 15 type Resp struct { 16 Did string `json:"did"` 17 } ··· 30 ctx := context.WithValue(e.Request().Context(), "skip-cache", true) 31 did, err := s.passport.ResolveHandle(ctx, parsed.String()) 32 if err != nil { 33 + logger.Error("error resolving handle", "error", err) 34 return helpers.ServerError(e, nil) 35 } 36
+34 -9
server/handle_server_update_email.go
··· 11 type ComAtprotoServerUpdateEmailRequest struct { 12 Email string `json:"email" validate:"required"` 13 EmailAuthFactor bool `json:"emailAuthFactor"` 14 - Token string `json:"token" validate:"required"` 15 } 16 17 func (s *Server) handleServerUpdateEmail(e echo.Context) error { 18 urepo := e.Get("repo").(*models.RepoActor) 19 20 var req ComAtprotoServerUpdateEmailRequest 21 if err := e.Bind(&req); err != nil { 22 - s.logger.Error("error binding", "error", err) 23 return helpers.ServerError(e, nil) 24 } 25 ··· 27 return helpers.InputError(e, nil) 28 } 29 30 - if urepo.EmailUpdateCode == nil || urepo.EmailUpdateCodeExpiresAt == nil { 31 return helpers.InvalidTokenError(e) 32 } 33 34 - if *urepo.EmailUpdateCode != req.Token { 35 - return helpers.InvalidTokenError(e) 36 } 37 38 - if time.Now().UTC().After(*urepo.EmailUpdateCodeExpiresAt) { 39 - return helpers.ExpiredTokenError(e) 40 } 41 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) 44 return helpers.ServerError(e, nil) 45 } 46
··· 11 type ComAtprotoServerUpdateEmailRequest struct { 12 Email string `json:"email" validate:"required"` 13 EmailAuthFactor bool `json:"emailAuthFactor"` 14 + Token string `json:"token"` 15 } 16 17 func (s *Server) handleServerUpdateEmail(e echo.Context) error { 18 + ctx := e.Request().Context() 19 + logger := s.logger.With("name", "handleServerUpdateEmail") 20 + 21 urepo := e.Get("repo").(*models.RepoActor) 22 23 var req ComAtprotoServerUpdateEmailRequest 24 if err := e.Bind(&req); err != nil { 25 + logger.Error("error binding", "error", err) 26 return helpers.ServerError(e, nil) 27 } 28 ··· 30 return helpers.InputError(e, nil) 31 } 32 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 == "" { 37 return helpers.InvalidTokenError(e) 38 } 39 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 57 } 58 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" 63 } 64 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) 69 return helpers.ServerError(e, nil) 70 } 71
+14 -11
server/handle_sync_get_blob.go
··· 17 ) 18 19 func (s *Server) handleSyncGetBlob(e echo.Context) error { 20 did := e.QueryParam("did") 21 if did == "" { 22 return helpers.InputError(e, nil) ··· 32 return helpers.InputError(e, nil) 33 } 34 35 - urepo, err := s.getRepoActorByDid(did) 36 if err != nil { 37 - s.logger.Error("could not find user for requested blob", "error", err) 38 return helpers.InputError(e, nil) 39 } 40 ··· 46 } 47 48 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) 51 return helpers.ServerError(e, nil) 52 } 53 ··· 55 56 if blob.Storage == "sqlite" { 57 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) 60 return helpers.ServerError(e, nil) 61 } 62 ··· 66 } 67 } else if blob.Storage == "s3" { 68 if !(s.s3Config != nil && s.s3Config.BlobstoreEnabled) { 69 - s.logger.Error("s3 storage disabled") 70 return helpers.ServerError(e, nil) 71 } 72 ··· 89 90 sess, err := session.NewSession(config) 91 if err != nil { 92 - s.logger.Error("error creating aws session", "error", err) 93 return helpers.ServerError(e, nil) 94 } 95 ··· 98 Bucket: aws.String(s.s3Config.Bucket), 99 Key: aws.String(blobKey), 100 }); err != nil { 101 - s.logger.Error("error getting blob from s3", "error", err) 102 return helpers.ServerError(e, nil) 103 } else { 104 read := 0 ··· 112 break 113 } 114 } else if err != nil && err != io.ErrUnexpectedEOF { 115 - s.logger.Error("error reading blob", "error", err) 116 return helpers.ServerError(e, nil) 117 } 118 ··· 123 } 124 } 125 } else { 126 - s.logger.Error("unknown storage", "storage", blob.Storage) 127 return helpers.ServerError(e, nil) 128 } 129
··· 17 ) 18 19 func (s *Server) handleSyncGetBlob(e echo.Context) error { 20 + ctx := e.Request().Context() 21 + logger := s.logger.With("name", "handleSyncGetBlob") 22 + 23 did := e.QueryParam("did") 24 if did == "" { 25 return helpers.InputError(e, nil) ··· 35 return helpers.InputError(e, nil) 36 } 37 38 + urepo, err := s.getRepoActorByDid(ctx, did) 39 if err != nil { 40 + logger.Error("could not find user for requested blob", "error", err) 41 return helpers.InputError(e, nil) 42 } 43 ··· 49 } 50 51 var blob models.Blob 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) 54 return helpers.ServerError(e, nil) 55 } 56 ··· 58 59 if blob.Storage == "sqlite" { 60 var parts []models.BlobPart 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) 63 return helpers.ServerError(e, nil) 64 } 65 ··· 69 } 70 } else if blob.Storage == "s3" { 71 if !(s.s3Config != nil && s.s3Config.BlobstoreEnabled) { 72 + logger.Error("s3 storage disabled") 73 return helpers.ServerError(e, nil) 74 } 75 ··· 92 93 sess, err := session.NewSession(config) 94 if err != nil { 95 + logger.Error("error creating aws session", "error", err) 96 return helpers.ServerError(e, nil) 97 } 98 ··· 101 Bucket: aws.String(s.s3Config.Bucket), 102 Key: aws.String(blobKey), 103 }); err != nil { 104 + logger.Error("error getting blob from s3", "error", err) 105 return helpers.ServerError(e, nil) 106 } else { 107 read := 0 ··· 115 break 116 } 117 } else if err != nil && err != io.ErrUnexpectedEOF { 118 + logger.Error("error reading blob", "error", err) 119 return helpers.ServerError(e, nil) 120 } 121 ··· 126 } 127 } 128 } else { 129 + logger.Error("unknown storage", "storage", blob.Storage) 130 return helpers.ServerError(e, nil) 131 } 132
+3 -2
server/handle_sync_get_blocks.go
··· 18 19 func (s *Server) handleGetBlocks(e echo.Context) error { 20 ctx := e.Request().Context() 21 22 var req ComAtprotoSyncGetBlocksRequest 23 if err := e.Bind(&req); err != nil { ··· 35 cids = append(cids, c) 36 } 37 38 - urepo, err := s.getRepoActorByDid(req.Did) 39 if err != nil { 40 return helpers.ServerError(e, nil) 41 } ··· 52 }) 53 54 if _, err := carstore.LdWrite(buf, hb); err != nil { 55 - s.logger.Error("error writing to car", "error", err) 56 return helpers.ServerError(e, nil) 57 } 58
··· 18 19 func (s *Server) handleGetBlocks(e echo.Context) error { 20 ctx := e.Request().Context() 21 + logger := s.logger.With("name", "handleSyncGetBlocks") 22 23 var req ComAtprotoSyncGetBlocksRequest 24 if err := e.Bind(&req); err != nil { ··· 36 cids = append(cids, c) 37 } 38 39 + urepo, err := s.getRepoActorByDid(ctx, req.Did) 40 if err != nil { 41 return helpers.ServerError(e, nil) 42 } ··· 53 }) 54 55 if _, err := carstore.LdWrite(buf, hb); err != nil { 56 + logger.Error("error writing to car", "error", err) 57 return helpers.ServerError(e, nil) 58 } 59
+3 -1
server/handle_sync_get_latest_commit.go
··· 12 } 13 14 func (s *Server) handleSyncGetLatestCommit(e echo.Context) error { 15 did := e.QueryParam("did") 16 if did == "" { 17 return helpers.InputError(e, nil) 18 } 19 20 - urepo, err := s.getRepoActorByDid(did) 21 if err != nil { 22 return err 23 }
··· 12 } 13 14 func (s *Server) handleSyncGetLatestCommit(e echo.Context) error { 15 + ctx := e.Request().Context() 16 + 17 did := e.QueryParam("did") 18 if did == "" { 19 return helpers.InputError(e, nil) 20 } 21 22 + urepo, err := s.getRepoActorByDid(ctx, did) 23 if err != nil { 24 return err 25 }
+8 -5
server/handle_sync_get_record.go
··· 13 ) 14 15 func (s *Server) handleSyncGetRecord(e echo.Context) error { 16 did := e.QueryParam("did") 17 collection := e.QueryParam("collection") 18 rkey := e.QueryParam("rkey") 19 20 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) 23 return helpers.ServerError(e, nil) 24 } 25 26 - root, blocks, err := s.repoman.getRecordProof(urepo, collection, rkey) 27 if err != nil { 28 return err 29 } ··· 36 }) 37 38 if _, err := carstore.LdWrite(buf, hb); err != nil { 39 - s.logger.Error("error writing to car", "error", err) 40 return helpers.ServerError(e, nil) 41 } 42 43 for _, blk := range blocks { 44 if _, err := carstore.LdWrite(buf, blk.Cid().Bytes(), blk.RawData()); err != nil { 45 - s.logger.Error("error writing to car", "error", err) 46 return helpers.ServerError(e, nil) 47 } 48 }
··· 13 ) 14 15 func (s *Server) handleSyncGetRecord(e echo.Context) error { 16 + ctx := e.Request().Context() 17 + logger := s.logger.With("name", "handleSyncGetRecord") 18 + 19 did := e.QueryParam("did") 20 collection := e.QueryParam("collection") 21 rkey := e.QueryParam("rkey") 22 23 var urepo models.Repo 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) 26 return helpers.ServerError(e, nil) 27 } 28 29 + root, blocks, err := s.repoman.getRecordProof(ctx, urepo, collection, rkey) 30 if err != nil { 31 return err 32 } ··· 39 }) 40 41 if _, err := carstore.LdWrite(buf, hb); err != nil { 42 + logger.Error("error writing to car", "error", err) 43 return helpers.ServerError(e, nil) 44 } 45 46 for _, blk := range blocks { 47 if _, err := carstore.LdWrite(buf, blk.Cid().Bytes(), blk.RawData()); err != nil { 48 + logger.Error("error writing to car", "error", err) 49 return helpers.ServerError(e, nil) 50 } 51 }
+6 -3
server/handle_sync_get_repo.go
··· 13 ) 14 15 func (s *Server) handleSyncGetRepo(e echo.Context) error { 16 did := e.QueryParam("did") 17 if did == "" { 18 return helpers.InputError(e, nil) 19 } 20 21 - urepo, err := s.getRepoActorByDid(did) 22 if err != nil { 23 return err 24 } ··· 36 buf := new(bytes.Buffer) 37 38 if _, err := carstore.LdWrite(buf, hb); err != nil { 39 - s.logger.Error("error writing to car", "error", err) 40 return helpers.ServerError(e, nil) 41 } 42 43 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 { 45 return err 46 } 47
··· 13 ) 14 15 func (s *Server) handleSyncGetRepo(e echo.Context) error { 16 + ctx := e.Request().Context() 17 + logger := s.logger.With("name", "handleSyncGetRepo") 18 + 19 did := e.QueryParam("did") 20 if did == "" { 21 return helpers.InputError(e, nil) 22 } 23 24 + urepo, err := s.getRepoActorByDid(ctx, did) 25 if err != nil { 26 return err 27 } ··· 39 buf := new(bytes.Buffer) 40 41 if _, err := carstore.LdWrite(buf, hb); err != nil { 42 + logger.Error("error writing to car", "error", err) 43 return helpers.ServerError(e, nil) 44 } 45 46 var blocks []models.Block 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 { 48 return err 49 } 50
+3 -1
server/handle_sync_get_repo_status.go
··· 14 15 // TODO: make this actually do the right thing 16 func (s *Server) handleSyncGetRepoStatus(e echo.Context) error { 17 did := e.QueryParam("did") 18 if did == "" { 19 return helpers.InputError(e, nil) 20 } 21 22 - urepo, err := s.getRepoActorByDid(did) 23 if err != nil { 24 return err 25 }
··· 14 15 // TODO: make this actually do the right thing 16 func (s *Server) handleSyncGetRepoStatus(e echo.Context) error { 17 + ctx := e.Request().Context() 18 + 19 did := e.QueryParam("did") 20 if did == "" { 21 return helpers.InputError(e, nil) 22 } 23 24 + urepo, err := s.getRepoActorByDid(ctx, did) 25 if err != nil { 26 return err 27 }
+8 -5
server/handle_sync_list_blobs.go
··· 14 } 15 16 func (s *Server) handleSyncListBlobs(e echo.Context) error { 17 did := e.QueryParam("did") 18 if did == "" { 19 return helpers.InputError(e, nil) ··· 35 } 36 params = append(params, limit) 37 38 - urepo, err := s.getRepoActorByDid(did) 39 if err != nil { 40 - s.logger.Error("could not find user for requested blobs", "error", err) 41 return helpers.InputError(e, nil) 42 } 43 ··· 49 } 50 51 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) 54 return helpers.ServerError(e, nil) 55 } 56 ··· 58 for _, b := range blobs { 59 c, err := cid.Cast(b.Cid) 60 if err != nil { 61 - s.logger.Error("error casting cid", "error", err) 62 return helpers.ServerError(e, nil) 63 } 64 cstrs = append(cstrs, c.String())
··· 14 } 15 16 func (s *Server) handleSyncListBlobs(e echo.Context) error { 17 + ctx := e.Request().Context() 18 + logger := s.logger.With("name", "handleSyncListBlobs") 19 + 20 did := e.QueryParam("did") 21 if did == "" { 22 return helpers.InputError(e, nil) ··· 38 } 39 params = append(params, limit) 40 41 + urepo, err := s.getRepoActorByDid(ctx, did) 42 if err != nil { 43 + logger.Error("could not find user for requested blobs", "error", err) 44 return helpers.InputError(e, nil) 45 } 46 ··· 52 } 53 54 var blobs []models.Blob 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) 57 return helpers.ServerError(e, nil) 58 } 59 ··· 61 for _, b := range blobs { 62 c, err := cid.Cast(b.Cid) 63 if err != nil { 64 + logger.Error("error casting cid", "error", err) 65 return helpers.ServerError(e, nil) 66 } 67 cstrs = append(cstrs, c.String())
+54 -42
server/handle_sync_subscribe_repos.go
··· 7 "github.com/bluesky-social/indigo/events" 8 "github.com/bluesky-social/indigo/lex/util" 9 "github.com/btcsuite/websocket" 10 "github.com/labstack/echo/v4" 11 ) 12 ··· 24 logger = logger.With("ident", ident) 25 logger.Info("new connection established") 26 27 evts, cancel, err := s.evtman.Subscribe(ctx, ident, func(evt *events.XRPCStreamEvent) bool { 28 return true 29 }, nil) ··· 34 35 header := events.EventHeader{Op: events.EvtKindMessage} 36 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 - } 42 43 - if ctx.Err() != nil { 44 - logger.Error("context error", "err", err) 45 - break 46 - } 47 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 - } 69 70 - if err := header.MarshalCBOR(wc); err != nil { 71 - logger.Error("failed to write header to relay", "err", err) 72 - break 73 - } 74 75 - if err := obj.MarshalCBOR(wc); err != nil { 76 - logger.Error("failed to write event to relay", "err", err) 77 - break 78 - } 79 80 - if err := wc.Close(); err != nil { 81 - logger.Error("failed to flush-close our event write", "err", err) 82 - break 83 - } 84 } 85 86 // we should tell the relay to request a new crawl at this point if we got disconnected
··· 7 "github.com/bluesky-social/indigo/events" 8 "github.com/bluesky-social/indigo/lex/util" 9 "github.com/btcsuite/websocket" 10 + "github.com/haileyok/cocoon/metrics" 11 "github.com/labstack/echo/v4" 12 ) 13 ··· 25 logger = logger.With("ident", ident) 26 logger.Info("new connection established") 27 28 + metrics.RelaysConnected.WithLabelValues(ident).Inc() 29 + defer func() { 30 + metrics.RelaysConnected.WithLabelValues(ident).Dec() 31 + }() 32 + 33 evts, cancel, err := s.evtman.Subscribe(ctx, ident, func(evt *events.XRPCStreamEvent) bool { 34 return true 35 }, nil) ··· 40 41 header := events.EventHeader{Op: events.EvtKindMessage} 42 for evt := range evts { 43 + func() { 44 + defer func() { 45 + metrics.RelaySends.WithLabelValues(ident, header.MsgType).Inc() 46 + }() 47 48 + wc, err := conn.NextWriter(websocket.BinaryMessage) 49 + if err != nil { 50 + logger.Error("error writing message to relay", "err", err) 51 + return 52 + } 53 54 + if ctx.Err() != nil { 55 + logger.Error("context error", "err", err) 56 + return 57 + } 58 59 + var obj util.CBOR 60 + switch { 61 + case evt.Error != nil: 62 + header.Op = events.EvtKindErrorFrame 63 + obj = evt.Error 64 + case evt.RepoCommit != nil: 65 + header.MsgType = "#commit" 66 + obj = evt.RepoCommit 67 + case evt.RepoIdentity != nil: 68 + header.MsgType = "#identity" 69 + obj = evt.RepoIdentity 70 + case evt.RepoAccount != nil: 71 + header.MsgType = "#account" 72 + obj = evt.RepoAccount 73 + case evt.RepoInfo != nil: 74 + header.MsgType = "#info" 75 + obj = evt.RepoInfo 76 + default: 77 + logger.Warn("unrecognized event kind") 78 + return 79 + } 80 81 + if err := header.MarshalCBOR(wc); err != nil { 82 + logger.Error("failed to write header to relay", "err", err) 83 + return 84 + } 85 86 + if err := obj.MarshalCBOR(wc); err != nil { 87 + logger.Error("failed to write event to relay", "err", err) 88 + return 89 + } 90 + 91 + if err := wc.Close(); err != nil { 92 + logger.Error("failed to flush-close our event write", "err", err) 93 + return 94 + } 95 + }() 96 } 97 98 // we should tell the relay to request a new crawl at this point if we got disconnected
+5 -2
server/handle_well_known.go
··· 67 } 68 69 func (s *Server) handleAtprotoDid(e echo.Context) error { 70 host := e.Request().Host 71 if host == "" { 72 return helpers.InputError(e, to.StringPtr("Invalid handle.")) ··· 84 return e.NoContent(404) 85 } 86 87 - actor, err := s.getActorByHandle(host) 88 if err != nil { 89 if err == gorm.ErrRecordNotFound { 90 return e.NoContent(404) 91 } 92 - s.logger.Error("error looking up actor by handle", "error", err) 93 return helpers.ServerError(e, nil) 94 } 95
··· 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.")) ··· 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
+19
server/mail.go
··· 96 97 return nil 98 }
··· 96 97 return nil 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 38 func (s *Server) handleLegacySessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 39 return func(e echo.Context) error { 40 authheader := e.Request().Header.Get("authorization") 41 if authheader == "" { 42 return e.JSON(401, map[string]string{"error": "Unauthorized"}) ··· 67 if hasLxm { 68 pts := strings.Split(e.Request().URL.String(), "/") 69 if lxm != pts[len(pts)-1] { 70 - s.logger.Error("service auth lxm incorrect", "lxm", lxm, "expected", pts[len(pts)-1], "error", err) 71 return helpers.InputError(e, nil) 72 } 73 74 maybeDid, ok := claims["iss"].(string) 75 if !ok { 76 - s.logger.Error("no iss in service auth token", "error", err) 77 return helpers.InputError(e, nil) 78 } 79 did = maybeDid 80 81 - maybeRepo, err := s.getRepoActorByDid(did) 82 if err != nil { 83 - s.logger.Error("error fetching repo", "error", err) 84 return helpers.ServerError(e, nil) 85 } 86 repo = maybeRepo ··· 94 return s.privateKey.Public(), nil 95 }) 96 if err != nil { 97 - s.logger.Error("error parsing jwt", "error", err) 98 return helpers.ExpiredTokenError(e) 99 } 100 ··· 107 hash := sha256.Sum256([]byte(signingInput)) 108 sigBytes, err := base64.RawURLEncoding.DecodeString(kpts[2]) 109 if err != nil { 110 - s.logger.Error("error decoding signature bytes", "error", err) 111 return helpers.ServerError(e, nil) 112 } 113 114 if len(sigBytes) != 64 { 115 - s.logger.Error("incorrect sigbytes length", "length", len(sigBytes)) 116 return helpers.ServerError(e, nil) 117 } 118 ··· 120 sBytes := sigBytes[32:] 121 rr, _ := secp256k1.NewScalarFromBytes((*[32]byte)(rBytes)) 122 ss, _ := secp256k1.NewScalarFromBytes((*[32]byte)(sBytes)) 123 124 sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey) 125 if err != nil { 126 - s.logger.Error("can't load private key", "error", err) 127 return err 128 } 129 130 pubKey, ok := sk.Public().(*secp256k1secec.PublicKey) 131 if !ok { 132 - s.logger.Error("error getting public key from sk") 133 return helpers.ServerError(e, nil) 134 } 135 136 verified := pubKey.VerifyRaw(hash[:], rr, ss) 137 if !verified { 138 - s.logger.Error("error verifying", "error", err) 139 return helpers.ServerError(e, nil) 140 } 141 } ··· 159 Found bool 160 } 161 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 { 163 if err == gorm.ErrRecordNotFound { 164 return helpers.InvalidTokenError(e) 165 } 166 167 - s.logger.Error("error getting token from db", "error", err) 168 return helpers.ServerError(e, nil) 169 } 170 ··· 175 176 exp, ok := claims["exp"].(float64) 177 if !ok { 178 - s.logger.Error("error getting iat from token") 179 return helpers.ServerError(e, nil) 180 } 181 ··· 184 } 185 186 if repo == nil { 187 - maybeRepo, err := s.getRepoActorByDid(claims["sub"].(string)) 188 if err != nil { 189 - s.logger.Error("error fetching repo", "error", err) 190 return helpers.ServerError(e, nil) 191 } 192 repo = maybeRepo ··· 207 208 func (s *Server) handleOauthSessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 209 return func(e echo.Context) error { 210 authheader := e.Request().Header.Get("authorization") 211 if authheader == "" { 212 return e.JSON(401, map[string]string{"error": "Unauthorized"}) ··· 238 "error": "use_dpop_nonce", 239 }) 240 } 241 - s.logger.Error("invalid dpop proof", "error", err) 242 return helpers.InputError(e, nil) 243 } 244 245 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) 248 return helpers.InputError(e, nil) 249 } 250 ··· 253 } 254 255 if *oauthToken.Parameters.DpopJkt != proof.JKT { 256 - s.logger.Error("jkt mismatch", "token", oauthToken.Parameters.DpopJkt, "proof", proof.JKT) 257 return helpers.InputError(e, to.StringPtr("dpop jkt mismatch")) 258 } 259 ··· 266 }) 267 } 268 269 - repo, err := s.getRepoActorByDid(oauthToken.Sub) 270 if err != nil { 271 - s.logger.Error("could not find actor in db", "error", err) 272 return helpers.ServerError(e, nil) 273 } 274
··· 37 38 func (s *Server) handleLegacySessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 39 return func(e echo.Context) error { 40 + ctx := e.Request().Context() 41 + logger := s.logger.With("name", "handleLegacySessionMiddleware") 42 + 43 authheader := e.Request().Header.Get("authorization") 44 if authheader == "" { 45 return e.JSON(401, map[string]string{"error": "Unauthorized"}) ··· 70 if hasLxm { 71 pts := strings.Split(e.Request().URL.String(), "/") 72 if lxm != pts[len(pts)-1] { 73 + logger.Error("service auth lxm incorrect", "lxm", lxm, "expected", pts[len(pts)-1], "error", err) 74 return helpers.InputError(e, nil) 75 } 76 77 maybeDid, ok := claims["iss"].(string) 78 if !ok { 79 + logger.Error("no iss in service auth token", "error", err) 80 return helpers.InputError(e, nil) 81 } 82 did = maybeDid 83 84 + maybeRepo, err := s.getRepoActorByDid(ctx, did) 85 if err != nil { 86 + logger.Error("error fetching repo", "error", err) 87 return helpers.ServerError(e, nil) 88 } 89 repo = maybeRepo ··· 97 return s.privateKey.Public(), nil 98 }) 99 if err != nil { 100 + logger.Error("error parsing jwt", "error", err) 101 return helpers.ExpiredTokenError(e) 102 } 103 ··· 110 hash := sha256.Sum256([]byte(signingInput)) 111 sigBytes, err := base64.RawURLEncoding.DecodeString(kpts[2]) 112 if err != nil { 113 + logger.Error("error decoding signature bytes", "error", err) 114 return helpers.ServerError(e, nil) 115 } 116 117 if len(sigBytes) != 64 { 118 + logger.Error("incorrect sigbytes length", "length", len(sigBytes)) 119 return helpers.ServerError(e, nil) 120 } 121 ··· 123 sBytes := sigBytes[32:] 124 rr, _ := secp256k1.NewScalarFromBytes((*[32]byte)(rBytes)) 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 + } 141 142 sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey) 143 if err != nil { 144 + logger.Error("can't load private key", "error", err) 145 return err 146 } 147 148 pubKey, ok := sk.Public().(*secp256k1secec.PublicKey) 149 if !ok { 150 + logger.Error("error getting public key from sk") 151 return helpers.ServerError(e, nil) 152 } 153 154 verified := pubKey.VerifyRaw(hash[:], rr, ss) 155 if !verified { 156 + logger.Error("error verifying", "error", err) 157 return helpers.ServerError(e, nil) 158 } 159 } ··· 177 Found bool 178 } 179 var result Result 180 + if err := s.db.Raw(ctx, "SELECT EXISTS(SELECT 1 FROM "+table+" WHERE token = ?) AS found", nil, tokenstr).Scan(&result).Error; err != nil { 181 if err == gorm.ErrRecordNotFound { 182 return helpers.InvalidTokenError(e) 183 } 184 185 + logger.Error("error getting token from db", "error", err) 186 return helpers.ServerError(e, nil) 187 } 188 ··· 193 194 exp, ok := claims["exp"].(float64) 195 if !ok { 196 + logger.Error("error getting iat from token") 197 return helpers.ServerError(e, nil) 198 } 199 ··· 202 } 203 204 if repo == nil { 205 + maybeRepo, err := s.getRepoActorByDid(ctx, claims["sub"].(string)) 206 if err != nil { 207 + logger.Error("error fetching repo", "error", err) 208 return helpers.ServerError(e, nil) 209 } 210 repo = maybeRepo ··· 225 226 func (s *Server) handleOauthSessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 227 return func(e echo.Context) error { 228 + ctx := e.Request().Context() 229 + logger := s.logger.With("name", "handleOauthSessionMiddleware") 230 + 231 authheader := e.Request().Header.Get("authorization") 232 if authheader == "" { 233 return e.JSON(401, map[string]string{"error": "Unauthorized"}) ··· 259 "error": "use_dpop_nonce", 260 }) 261 } 262 + logger.Error("invalid dpop proof", "error", err) 263 return helpers.InputError(e, nil) 264 } 265 266 var oauthToken provider.OauthToken 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) 269 return helpers.InputError(e, nil) 270 } 271 ··· 274 } 275 276 if *oauthToken.Parameters.DpopJkt != proof.JKT { 277 + logger.Error("jkt mismatch", "token", oauthToken.Parameters.DpopJkt, "proof", proof.JKT) 278 return helpers.InputError(e, to.StringPtr("dpop jkt mismatch")) 279 } 280 ··· 287 }) 288 } 289 290 + repo, err := s.getRepoActorByDid(ctx, oauthToken.Sub) 291 if err != nil { 292 + logger.Error("could not find actor in db", "error", err) 293 return helpers.ServerError(e, nil) 294 } 295
+85 -32
server/repo.go
··· 17 lexutil "github.com/bluesky-social/indigo/lex/util" 18 "github.com/bluesky-social/indigo/repo" 19 "github.com/haileyok/cocoon/internal/db" 20 "github.com/haileyok/cocoon/models" 21 "github.com/haileyok/cocoon/recording_blockstore" 22 blocks "github.com/ipfs/go-block-format" ··· 96 } 97 98 // TODO make use of swap commit 99 - func (rm *RepoMan) applyWrites(urepo models.Repo, writes []Op, swapCommit *string) ([]ApplyWriteResult, error) { 100 rootcid, err := cid.Cast(urepo.Root) 101 if err != nil { 102 return nil, err ··· 104 105 dbs := rm.s.getBlockstore(urepo.Did) 106 bs := recording_blockstore.New(dbs) 107 - r, err := repo.OpenRepo(context.TODO(), bs, rootcid) 108 109 - entries := []models.Record{} 110 var results []ApplyWriteResult 111 112 for i, op := range writes { 113 if op.Type != OpTypeCreate && op.Rkey == nil { 114 return nil, fmt.Errorf("invalid rkey") 115 } else if op.Type == OpTypeCreate && op.Rkey != nil { 116 - _, _, err := r.GetRecord(context.TODO(), op.Collection+"/"+*op.Rkey) 117 if err == nil { 118 op.Type = OpTypeUpdate 119 } 120 } else if op.Rkey == nil { 121 op.Rkey = to.StringPtr(rm.clock.Next().String()) 122 writes[i].Rkey = op.Rkey 123 } 124 125 _, err := syntax.ParseRecordKey(*op.Rkey) 126 if err != nil { 127 return nil, err ··· 129 130 switch op.Type { 131 case OpTypeCreate: 132 - j, err := json.Marshal(*op.Record) 133 if err != nil { 134 return nil, err 135 } 136 - out, err := atdata.UnmarshalJSON(j) 137 if err != nil { 138 return nil, err 139 } 140 mm := MarshalableMap(out) 141 142 // HACK: if a record doesn't contain a $type, we can manually set it here based on the op's collection 143 if mm["$type"] == "" { 144 mm["$type"] = op.Collection 145 } 146 147 - nc, err := r.PutRecord(context.TODO(), op.Collection+"/"+*op.Rkey, &mm) 148 if err != nil { 149 return nil, err 150 } 151 d, err := atdata.MarshalCBOR(mm) 152 if err != nil { 153 return nil, err 154 } 155 entries = append(entries, models.Record{ 156 Did: urepo.Did, 157 CreatedAt: rm.clock.Next().String(), ··· 160 Cid: nc.String(), 161 Value: d, 162 }) 163 results = append(results, ApplyWriteResult{ 164 Type: to.StringPtr(OpTypeCreate.String()), 165 Uri: to.StringPtr("at://" + urepo.Did + "/" + op.Collection + "/" + *op.Rkey), ··· 167 ValidationStatus: to.StringPtr("valid"), // TODO: obviously this might not be true atm lol 168 }) 169 case OpTypeDelete: 170 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 { 172 return nil, err 173 } 174 entries = append(entries, models.Record{ 175 Did: urepo.Did, 176 Nsid: op.Collection, 177 Rkey: *op.Rkey, 178 Value: old.Value, 179 }) 180 - err := r.DeleteRecord(context.TODO(), op.Collection+"/"+*op.Rkey) 181 if err != nil { 182 return nil, err 183 } 184 results = append(results, ApplyWriteResult{ 185 Type: to.StringPtr(OpTypeDelete.String()), 186 }) 187 case OpTypeUpdate: 188 - j, err := json.Marshal(*op.Record) 189 if err != nil { 190 return nil, err 191 } 192 - out, err := atdata.UnmarshalJSON(j) 193 if err != nil { 194 return nil, err 195 } 196 mm := MarshalableMap(out) 197 - nc, err := r.UpdateRecord(context.TODO(), op.Collection+"/"+*op.Rkey, &mm) 198 if err != nil { 199 return nil, err 200 } 201 d, err := atdata.MarshalCBOR(mm) 202 if err != nil { 203 return nil, err 204 } 205 entries = append(entries, models.Record{ 206 Did: urepo.Did, 207 CreatedAt: rm.clock.Next().String(), ··· 210 Cid: nc.String(), 211 Value: d, 212 }) 213 results = append(results, ApplyWriteResult{ 214 Type: to.StringPtr(OpTypeUpdate.String()), 215 Uri: to.StringPtr("at://" + urepo.Did + "/" + op.Collection + "/" + *op.Rkey), ··· 219 } 220 } 221 222 - newroot, rev, err := r.Commit(context.TODO(), urepo.SignFor) 223 if err != nil { 224 return nil, err 225 } 226 227 buf := new(bytes.Buffer) 228 229 hb, err := cbor.DumpObject(&car.CarHeader{ 230 Roots: []cid.Cid{newroot}, 231 Version: 1, 232 }) 233 - 234 if _, err := carstore.LdWrite(buf, hb); err != nil { 235 return nil, err 236 } 237 238 - diffops, err := r.DiffSince(context.TODO(), rootcid) 239 if err != nil { 240 return nil, err 241 } 242 243 ops := make([]*atproto.SyncSubscribeRepos_RepoOp, 0, len(diffops)) 244 - 245 for _, op := range diffops { 246 var c cid.Cid 247 switch op.Op { ··· 270 }) 271 } 272 273 - blk, err := dbs.Get(context.TODO(), c) 274 if err != nil { 275 return nil, err 276 } 277 278 if _, err := carstore.LdWrite(buf, blk.Cid().Bytes(), blk.RawData()); err != nil { 279 return nil, err 280 } 281 } 282 283 for _, op := range bs.GetWriteLog() { 284 if _, err := carstore.LdWrite(buf, op.Cid().Bytes(), op.RawData()); err != nil { 285 return nil, err 286 } 287 } 288 289 var blobs []lexutil.LexLink 290 for _, entry := range entries { 291 var cids []cid.Cid 292 if entry.Cid != "" { 293 - if err := rm.s.db.Create(&entry, []clause.Expression{clause.OnConflict{ 294 Columns: []clause.Column{{Name: "did"}, {Name: "nsid"}, {Name: "rkey"}}, 295 UpdateAll: true, 296 }}).Error; err != nil { 297 return nil, err 298 } 299 300 - cids, err = rm.incrementBlobRefs(urepo, entry.Value) 301 if err != nil { 302 return nil, err 303 } 304 } else { 305 - if err := rm.s.db.Delete(&entry, nil).Error; err != nil { 306 return nil, err 307 } 308 - cids, err = rm.decrementBlobRefs(urepo, entry.Value) 309 if err != nil { 310 return nil, err 311 } 312 } 313 314 for _, c := range cids { 315 blobs = append(blobs, lexutil.LexLink(c)) 316 } 317 } 318 319 - rm.s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 320 RepoCommit: &atproto.SyncSubscribeRepos_Commit{ 321 Repo: urepo.Did, 322 Blocks: buf.Bytes(), ··· 330 }, 331 }) 332 333 - if err := rm.s.UpdateRepo(context.TODO(), urepo.Did, newroot, rev); err != nil { 334 return nil, err 335 } 336 ··· 345 return results, nil 346 } 347 348 - func (rm *RepoMan) getRecordProof(urepo models.Repo, collection, rkey string) (cid.Cid, []blocks.Block, error) { 349 c, err := cid.Cast(urepo.Root) 350 if err != nil { 351 return cid.Undef, nil, err ··· 354 dbs := rm.s.getBlockstore(urepo.Did) 355 bs := recording_blockstore.New(dbs) 356 357 - r, err := repo.OpenRepo(context.TODO(), bs, c) 358 if err != nil { 359 return cid.Undef, nil, err 360 } 361 362 - _, _, err = r.GetRecordBytes(context.TODO(), collection+"/"+rkey) 363 if err != nil { 364 return cid.Undef, nil, err 365 } ··· 367 return c, bs.GetReadLog(), nil 368 } 369 370 - func (rm *RepoMan) incrementBlobRefs(urepo models.Repo, cbor []byte) ([]cid.Cid, error) { 371 cids, err := getBlobCidsFromCbor(cbor) 372 if err != nil { 373 return nil, err 374 } 375 376 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 { 378 return nil, err 379 } 380 } ··· 382 return cids, nil 383 } 384 385 - func (rm *RepoMan) decrementBlobRefs(urepo models.Repo, cbor []byte) ([]cid.Cid, error) { 386 cids, err := getBlobCidsFromCbor(cbor) 387 if err != nil { 388 return nil, err ··· 393 ID uint 394 Count int 395 } 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 { 397 return nil, err 398 } 399 400 if res.Count == 0 { 401 - if err := rm.db.Exec("DELETE FROM blobs WHERE id = ?", nil, res.ID).Error; err != nil { 402 return nil, err 403 } 404 - if err := rm.db.Exec("DELETE FROM blob_parts WHERE blob_id = ?", nil, res.ID).Error; err != nil { 405 return nil, err 406 } 407 }
··· 17 lexutil "github.com/bluesky-social/indigo/lex/util" 18 "github.com/bluesky-social/indigo/repo" 19 "github.com/haileyok/cocoon/internal/db" 20 + "github.com/haileyok/cocoon/metrics" 21 "github.com/haileyok/cocoon/models" 22 "github.com/haileyok/cocoon/recording_blockstore" 23 blocks "github.com/ipfs/go-block-format" ··· 97 } 98 99 // TODO make use of swap commit 100 + func (rm *RepoMan) applyWrites(ctx context.Context, urepo models.Repo, writes []Op, swapCommit *string) ([]ApplyWriteResult, error) { 101 rootcid, err := cid.Cast(urepo.Root) 102 if err != nil { 103 return nil, err ··· 105 106 dbs := rm.s.getBlockstore(urepo.Did) 107 bs := recording_blockstore.New(dbs) 108 + r, err := repo.OpenRepo(ctx, bs, rootcid) 109 110 var results []ApplyWriteResult 111 112 + entries := make([]models.Record, 0, len(writes)) 113 for i, op := range writes { 114 + // updates or deletes must supply an rkey 115 if op.Type != OpTypeCreate && op.Rkey == nil { 116 return nil, fmt.Errorf("invalid rkey") 117 } else if op.Type == OpTypeCreate && op.Rkey != nil { 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)) 120 if err == nil { 121 op.Type = OpTypeUpdate 122 } 123 } else if op.Rkey == nil { 124 + // creates that don't supply an rkey will have one generated for them 125 op.Rkey = to.StringPtr(rm.clock.Next().String()) 126 writes[i].Rkey = op.Rkey 127 } 128 129 + // validate the record key is actually valid 130 _, err := syntax.ParseRecordKey(*op.Rkey) 131 if err != nil { 132 return nil, err ··· 134 135 switch op.Type { 136 case OpTypeCreate: 137 + // HACK: this fixes some type conversions, mainly around integers 138 + // first we convert to json bytes 139 + b, err := json.Marshal(*op.Record) 140 if err != nil { 141 return nil, err 142 } 143 + // then we use atdata.UnmarshalJSON to convert it back to a map 144 + out, err := atdata.UnmarshalJSON(b) 145 if err != nil { 146 return nil, err 147 } 148 + // finally we can cast to a MarshalableMap 149 mm := MarshalableMap(out) 150 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? 153 if mm["$type"] == "" { 154 mm["$type"] = op.Collection 155 } 156 157 + nc, err := r.PutRecord(ctx, fmt.Sprintf("%s/%s", op.Collection, *op.Rkey), &mm) 158 if err != nil { 159 return nil, err 160 } 161 + 162 d, err := atdata.MarshalCBOR(mm) 163 if err != nil { 164 return nil, err 165 } 166 + 167 entries = append(entries, models.Record{ 168 Did: urepo.Did, 169 CreatedAt: rm.clock.Next().String(), ··· 172 Cid: nc.String(), 173 Value: d, 174 }) 175 + 176 results = append(results, ApplyWriteResult{ 177 Type: to.StringPtr(OpTypeCreate.String()), 178 Uri: to.StringPtr("at://" + urepo.Did + "/" + op.Collection + "/" + *op.Rkey), ··· 180 ValidationStatus: to.StringPtr("valid"), // TODO: obviously this might not be true atm lol 181 }) 182 case OpTypeDelete: 183 + // try to find the old record in the database 184 var old models.Record 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 { 186 return nil, err 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 192 entries = append(entries, models.Record{ 193 Did: urepo.Did, 194 Nsid: op.Collection, 195 Rkey: *op.Rkey, 196 Value: old.Value, 197 }) 198 + 199 + // delete the record from the repo 200 + err := r.DeleteRecord(ctx, fmt.Sprintf("%s/%s", op.Collection, *op.Rkey)) 201 if err != nil { 202 return nil, err 203 } 204 + 205 + // add a result for the delete 206 results = append(results, ApplyWriteResult{ 207 Type: to.StringPtr(OpTypeDelete.String()), 208 }) 209 case OpTypeUpdate: 210 + // HACK: same hack as above for type fixes 211 + b, err := json.Marshal(*op.Record) 212 if err != nil { 213 return nil, err 214 } 215 + out, err := atdata.UnmarshalJSON(b) 216 if err != nil { 217 return nil, err 218 } 219 mm := MarshalableMap(out) 220 + 221 + nc, err := r.UpdateRecord(ctx, fmt.Sprintf("%s/%s", op.Collection, *op.Rkey), &mm) 222 if err != nil { 223 return nil, err 224 } 225 + 226 d, err := atdata.MarshalCBOR(mm) 227 if err != nil { 228 return nil, err 229 } 230 + 231 entries = append(entries, models.Record{ 232 Did: urepo.Did, 233 CreatedAt: rm.clock.Next().String(), ··· 236 Cid: nc.String(), 237 Value: d, 238 }) 239 + 240 results = append(results, ApplyWriteResult{ 241 Type: to.StringPtr(OpTypeUpdate.String()), 242 Uri: to.StringPtr("at://" + urepo.Did + "/" + op.Collection + "/" + *op.Rkey), ··· 246 } 247 } 248 249 + // commit and get the new root 250 + newroot, rev, err := r.Commit(ctx, urepo.SignFor) 251 if err != nil { 252 return nil, err 253 } 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 262 buf := new(bytes.Buffer) 263 264 + // first write the car header to the buffer 265 hb, err := cbor.DumpObject(&car.CarHeader{ 266 Roots: []cid.Cid{newroot}, 267 Version: 1, 268 }) 269 if _, err := carstore.LdWrite(buf, hb); err != nil { 270 return nil, err 271 } 272 273 + // get a diff of the changes to the repo 274 + diffops, err := r.DiffSince(ctx, rootcid) 275 if err != nil { 276 return nil, err 277 } 278 279 + // create the repo ops for the given diff 280 ops := make([]*atproto.SyncSubscribeRepos_RepoOp, 0, len(diffops)) 281 for _, op := range diffops { 282 var c cid.Cid 283 switch op.Op { ··· 306 }) 307 } 308 309 + blk, err := dbs.Get(ctx, c) 310 if err != nil { 311 return nil, err 312 } 313 314 + // write the block to the buffer 315 if _, err := carstore.LdWrite(buf, blk.Cid().Bytes(), blk.RawData()); err != nil { 316 return nil, err 317 } 318 } 319 320 + // write the writelog to the buffer 321 for _, op := range bs.GetWriteLog() { 322 if _, err := carstore.LdWrite(buf, op.Cid().Bytes(), op.RawData()); err != nil { 323 return nil, err 324 } 325 } 326 327 + // blob blob blob blob blob :3 328 var blobs []lexutil.LexLink 329 for _, entry := range entries { 330 var cids []cid.Cid 331 + // whenever there is cid present, we know it's a create (dumb) 332 if entry.Cid != "" { 333 + if err := rm.s.db.Create(ctx, &entry, []clause.Expression{clause.OnConflict{ 334 Columns: []clause.Column{{Name: "did"}, {Name: "nsid"}, {Name: "rkey"}}, 335 UpdateAll: true, 336 }}).Error; err != nil { 337 return nil, err 338 } 339 340 + // increment the given blob refs, yay 341 + cids, err = rm.incrementBlobRefs(ctx, urepo, entry.Value) 342 if err != nil { 343 return nil, err 344 } 345 } else { 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 { 350 return nil, err 351 } 352 + 353 + // TODO: 354 + cids, err = rm.decrementBlobRefs(ctx, urepo, entry.Value) 355 if err != nil { 356 return nil, err 357 } 358 } 359 360 + // add all the relevant blobs to the blobs list of blobs. blob ^.^ 361 for _, c := range cids { 362 blobs = append(blobs, lexutil.LexLink(c)) 363 } 364 } 365 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{ 369 RepoCommit: &atproto.SyncSubscribeRepos_Commit{ 370 Repo: urepo.Did, 371 Blocks: buf.Bytes(), ··· 379 }, 380 }) 381 382 + if err := rm.s.UpdateRepo(ctx, urepo.Did, newroot, rev); err != nil { 383 return nil, err 384 } 385 ··· 394 return results, nil 395 } 396 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) { 400 c, err := cid.Cast(urepo.Root) 401 if err != nil { 402 return cid.Undef, nil, err ··· 405 dbs := rm.s.getBlockstore(urepo.Did) 406 bs := recording_blockstore.New(dbs) 407 408 + r, err := repo.OpenRepo(ctx, bs, c) 409 if err != nil { 410 return cid.Undef, nil, err 411 } 412 413 + _, _, err = r.GetRecordBytes(ctx, fmt.Sprintf("%s/%s", collection, rkey)) 414 if err != nil { 415 return cid.Undef, nil, err 416 } ··· 418 return c, bs.GetReadLog(), nil 419 } 420 421 + func (rm *RepoMan) incrementBlobRefs(ctx context.Context, urepo models.Repo, cbor []byte) ([]cid.Cid, error) { 422 cids, err := getBlobCidsFromCbor(cbor) 423 if err != nil { 424 return nil, err 425 } 426 427 for _, c := range cids { 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 { 429 return nil, err 430 } 431 } ··· 433 return cids, nil 434 } 435 436 + func (rm *RepoMan) decrementBlobRefs(ctx context.Context, urepo models.Repo, cbor []byte) ([]cid.Cid, error) { 437 cids, err := getBlobCidsFromCbor(cbor) 438 if err != nil { 439 return nil, err ··· 444 ID uint 445 Count int 446 } 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 { 448 return nil, err 449 } 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!!!! 453 if res.Count == 0 { 454 + if err := rm.db.Exec(ctx, "DELETE FROM blobs WHERE id = ?", nil, res.ID).Error; err != nil { 455 return nil, err 456 } 457 + if err := rm.db.Exec(ctx, "DELETE FROM blob_parts WHERE blob_id = ?", nil, res.ID).Error; err != nil { 458 return nil, err 459 } 460 }
+39 -28
server/server.go
··· 39 "github.com/haileyok/cocoon/oauth/provider" 40 "github.com/haileyok/cocoon/plc" 41 "github.com/ipfs/go-cid" 42 echo_session "github.com/labstack/echo-contrib/session" 43 "github.com/labstack/echo/v4" 44 "github.com/labstack/echo/v4/middleware" ··· 89 } 90 91 type Args struct { 92 Addr string 93 DbName string 94 DbType string 95 DatabaseURL string 96 - Logger *slog.Logger 97 Version string 98 Did string 99 Hostname string ··· 209 } 210 211 func New(args *Args) (*Server, error) { 212 if args.Addr == "" { 213 return nil, fmt.Errorf("addr must be set") 214 } ··· 237 return nil, fmt.Errorf("admin password must be set") 238 } 239 240 - if args.Logger == nil { 241 - args.Logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{})) 242 - } 243 - 244 if args.SessionSecret == "" { 245 panic("SESSION SECRET WAS NOT SET. THIS IS REQUIRED. ") 246 } ··· 248 e := echo.New() 249 250 e.Pre(middleware.RemoveTrailingSlash()) 251 - e.Pre(slogecho.New(args.Logger)) 252 e.Use(echo_session.Middleware(sessions.NewCookieStore([]byte(args.SessionSecret)))) 253 e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 254 AllowOrigins: []string{"*"}, 255 AllowHeaders: []string{"*"}, ··· 311 if err != nil { 312 return nil, fmt.Errorf("failed to connect to postgres: %w", err) 313 } 314 - args.Logger.Info("connected to PostgreSQL database") 315 default: 316 gdb, err = gorm.Open(sqlite.Open(args.DbName), &gorm.Config{}) 317 if err != nil { 318 return nil, fmt.Errorf("failed to open sqlite database: %w", err) 319 } 320 - args.Logger.Info("connected to SQLite database", "path", args.DbName) 321 } 322 dbw := db.NewDB(gdb) 323 ··· 360 var nonceSecret []byte 361 maybeSecret, err := os.ReadFile("nonce.secret") 362 if err != nil && !os.IsNotExist(err) { 363 - args.Logger.Error("error attempting to read nonce secret", "error", err) 364 } else { 365 nonceSecret = maybeSecret 366 } ··· 398 Hostname: args.Hostname, 399 ClientManagerArgs: client.ManagerArgs{ 400 Cli: oauthCli, 401 - Logger: args.Logger, 402 }, 403 DpopManagerArgs: dpop.ManagerArgs{ 404 NonceSecret: nonceSecret, 405 NonceRotationInterval: constants.NonceMaxRotationInterval / 3, 406 OnNonceSecretCreated: func(newNonce []byte) { 407 if err := os.WriteFile("nonce.secret", newNonce, 0644); err != nil { 408 - args.Logger.Error("error writing new nonce secret", "error", err) 409 } 410 }, 411 - Logger: args.Logger, 412 Hostname: args.Hostname, 413 }, 414 }), ··· 535 } 536 537 func (s *Server) Serve(ctx context.Context) error { 538 s.addRoutes() 539 540 - s.logger.Info("migrating...") 541 542 s.db.AutoMigrate( 543 &models.Actor{}, ··· 554 &provider.OauthAuthorizationRequest{}, 555 ) 556 557 - s.logger.Info("starting cocoon") 558 559 go func() { 560 if err := s.httpd.ListenAndServe(); err != nil { ··· 566 567 go func() { 568 if err := s.requestCrawl(ctx); err != nil { 569 - s.logger.Error("error requesting crawls", "err", err) 570 } 571 }() 572 ··· 584 585 logger.Info("requesting crawl with configured relays") 586 587 - if time.Now().Sub(s.lastRequestCrawl) <= 1*time.Minute { 588 return fmt.Errorf("a crawl request has already been made within the last minute") 589 } 590 ··· 607 } 608 609 func (s *Server) doBackup() { 610 if s.dbType == "postgres" { 611 - s.logger.Info("skipping S3 backup - PostgreSQL backups should be handled externally (pg_dump, managed database backups, etc.)") 612 return 613 } 614 615 start := time.Now() 616 617 - s.logger.Info("beginning backup to s3...") 618 619 var buf bytes.Buffer 620 if err := func() error { 621 - s.logger.Info("reading database bytes...") 622 s.db.Lock() 623 defer s.db.Unlock() 624 ··· 634 635 return nil 636 }(); err != nil { 637 - s.logger.Error("error backing up database", "error", err) 638 return 639 } 640 641 if err := func() error { 642 - s.logger.Info("sending to s3...") 643 644 currTime := time.Now().Format("2006-01-02_15-04-05") 645 key := "cocoon-backup-" + currTime + ".db" ··· 669 return fmt.Errorf("error uploading file to s3: %w", err) 670 } 671 672 - s.logger.Info("finished uploading backup to s3", "key", key, "duration", time.Now().Sub(start).Seconds()) 673 674 return nil 675 }(); err != nil { 676 - s.logger.Error("error uploading database backup", "error", err) 677 return 678 } 679 ··· 681 } 682 683 func (s *Server) backupRoutine() { 684 if s.s3Config == nil || !s.s3Config.BackupsEnabled { 685 return 686 } 687 688 if s.s3Config.Region == "" { 689 - s.logger.Warn("no s3 region configured but backups are enabled. backups will not run.") 690 return 691 } 692 693 if s.s3Config.Bucket == "" { 694 - s.logger.Warn("no s3 bucket configured but backups are enabled. backups will not run.") 695 return 696 } 697 698 if s.s3Config.AccessKey == "" { 699 - s.logger.Warn("no s3 access key configured but backups are enabled. backups will not run.") 700 return 701 } 702 703 if s.s3Config.SecretKey == "" { 704 - s.logger.Warn("no s3 secret key configured but backups are enabled. backups will not run.") 705 return 706 } 707 ··· 729 } 730 731 func (s *Server) UpdateRepo(ctx context.Context, did string, root cid.Cid, rev string) error { 732 - if err := s.db.Exec("UPDATE repos SET root = ?, rev = ? WHERE did = ?", nil, root.Bytes(), rev, did).Error; err != nil { 733 return err 734 } 735
··· 39 "github.com/haileyok/cocoon/oauth/provider" 40 "github.com/haileyok/cocoon/plc" 41 "github.com/ipfs/go-cid" 42 + "github.com/labstack/echo-contrib/echoprometheus" 43 echo_session "github.com/labstack/echo-contrib/session" 44 "github.com/labstack/echo/v4" 45 "github.com/labstack/echo/v4/middleware" ··· 90 } 91 92 type Args struct { 93 + Logger *slog.Logger 94 + 95 Addr string 96 DbName string 97 DbType string 98 DatabaseURL string 99 Version string 100 Did string 101 Hostname string ··· 211 } 212 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 + 220 if args.Addr == "" { 221 return nil, fmt.Errorf("addr must be set") 222 } ··· 245 return nil, fmt.Errorf("admin password must be set") 246 } 247 248 if args.SessionSecret == "" { 249 panic("SESSION SECRET WAS NOT SET. THIS IS REQUIRED. ") 250 } ··· 252 e := echo.New() 253 254 e.Pre(middleware.RemoveTrailingSlash()) 255 + e.Pre(slogecho.New(args.Logger.With("component", "slogecho"))) 256 e.Use(echo_session.Middleware(sessions.NewCookieStore([]byte(args.SessionSecret)))) 257 + e.Use(echoprometheus.NewMiddleware("cocoon")) 258 e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 259 AllowOrigins: []string{"*"}, 260 AllowHeaders: []string{"*"}, ··· 316 if err != nil { 317 return nil, fmt.Errorf("failed to connect to postgres: %w", err) 318 } 319 + logger.Info("connected to PostgreSQL database") 320 default: 321 gdb, err = gorm.Open(sqlite.Open(args.DbName), &gorm.Config{}) 322 if err != nil { 323 return nil, fmt.Errorf("failed to open sqlite database: %w", err) 324 } 325 + logger.Info("connected to SQLite database", "path", args.DbName) 326 } 327 dbw := db.NewDB(gdb) 328 ··· 365 var nonceSecret []byte 366 maybeSecret, err := os.ReadFile("nonce.secret") 367 if err != nil && !os.IsNotExist(err) { 368 + logger.Error("error attempting to read nonce secret", "error", err) 369 } else { 370 nonceSecret = maybeSecret 371 } ··· 403 Hostname: args.Hostname, 404 ClientManagerArgs: client.ManagerArgs{ 405 Cli: oauthCli, 406 + Logger: args.Logger.With("component", "oauth-client-manager"), 407 }, 408 DpopManagerArgs: dpop.ManagerArgs{ 409 NonceSecret: nonceSecret, 410 NonceRotationInterval: constants.NonceMaxRotationInterval / 3, 411 OnNonceSecretCreated: func(newNonce []byte) { 412 if err := os.WriteFile("nonce.secret", newNonce, 0644); err != nil { 413 + logger.Error("error writing new nonce secret", "error", err) 414 } 415 }, 416 + Logger: args.Logger.With("component", "dpop-manager"), 417 Hostname: args.Hostname, 418 }, 419 }), ··· 540 } 541 542 func (s *Server) Serve(ctx context.Context) error { 543 + logger := s.logger.With("name", "Serve") 544 + 545 s.addRoutes() 546 547 + logger.Info("migrating...") 548 549 s.db.AutoMigrate( 550 &models.Actor{}, ··· 561 &provider.OauthAuthorizationRequest{}, 562 ) 563 564 + logger.Info("starting cocoon") 565 566 go func() { 567 if err := s.httpd.ListenAndServe(); err != nil { ··· 573 574 go func() { 575 if err := s.requestCrawl(ctx); err != nil { 576 + logger.Error("error requesting crawls", "err", err) 577 } 578 }() 579 ··· 591 592 logger.Info("requesting crawl with configured relays") 593 594 + if time.Since(s.lastRequestCrawl) <= 1*time.Minute { 595 return fmt.Errorf("a crawl request has already been made within the last minute") 596 } 597 ··· 614 } 615 616 func (s *Server) doBackup() { 617 + logger := s.logger.With("name", "doBackup") 618 + 619 if s.dbType == "postgres" { 620 + logger.Info("skipping S3 backup - PostgreSQL backups should be handled externally (pg_dump, managed database backups, etc.)") 621 return 622 } 623 624 start := time.Now() 625 626 + logger.Info("beginning backup to s3...") 627 628 var buf bytes.Buffer 629 if err := func() error { 630 + logger.Info("reading database bytes...") 631 s.db.Lock() 632 defer s.db.Unlock() 633 ··· 643 644 return nil 645 }(); err != nil { 646 + logger.Error("error backing up database", "error", err) 647 return 648 } 649 650 if err := func() error { 651 + logger.Info("sending to s3...") 652 653 currTime := time.Now().Format("2006-01-02_15-04-05") 654 key := "cocoon-backup-" + currTime + ".db" ··· 678 return fmt.Errorf("error uploading file to s3: %w", err) 679 } 680 681 + logger.Info("finished uploading backup to s3", "key", key, "duration", time.Now().Sub(start).Seconds()) 682 683 return nil 684 }(); err != nil { 685 + logger.Error("error uploading database backup", "error", err) 686 return 687 } 688 ··· 690 } 691 692 func (s *Server) backupRoutine() { 693 + logger := s.logger.With("name", "backupRoutine") 694 + 695 if s.s3Config == nil || !s.s3Config.BackupsEnabled { 696 return 697 } 698 699 if s.s3Config.Region == "" { 700 + logger.Warn("no s3 region configured but backups are enabled. backups will not run.") 701 return 702 } 703 704 if s.s3Config.Bucket == "" { 705 + logger.Warn("no s3 bucket configured but backups are enabled. backups will not run.") 706 return 707 } 708 709 if s.s3Config.AccessKey == "" { 710 + logger.Warn("no s3 access key configured but backups are enabled. backups will not run.") 711 return 712 } 713 714 if s.s3Config.SecretKey == "" { 715 + logger.Warn("no s3 secret key configured but backups are enabled. backups will not run.") 716 return 717 } 718 ··· 740 } 741 742 func (s *Server) UpdateRepo(ctx context.Context, did string, root cid.Cid, rev string) error { 743 + if err := s.db.Exec(ctx, "UPDATE repos SET root = ?, rev = ? WHERE did = ?", nil, root.Bytes(), rev, did).Error; err != nil { 744 return err 745 } 746
+4 -3
server/session.go
··· 1 package server 2 3 import ( 4 "time" 5 6 "github.com/golang-jwt/jwt/v4" ··· 13 RefreshToken string 14 } 15 16 - func (s *Server) createSession(repo *models.Repo) (*Session, error) { 17 now := time.Now() 18 accexp := now.Add(3 * time.Hour) 19 refexp := now.Add(7 * 24 * time.Hour) ··· 49 return nil, err 50 } 51 52 - if err := s.db.Create(&models.Token{ 53 Token: accessString, 54 Did: repo.Did, 55 RefreshToken: refreshString, ··· 59 return nil, err 60 } 61 62 - if err := s.db.Create(&models.RefreshToken{ 63 Token: refreshString, 64 Did: repo.Did, 65 CreatedAt: now,
··· 1 package server 2 3 import ( 4 + "context" 5 "time" 6 7 "github.com/golang-jwt/jwt/v4" ··· 14 RefreshToken string 15 } 16 17 + func (s *Server) createSession(ctx context.Context, repo *models.Repo) (*Session, error) { 18 now := time.Now() 19 accexp := now.Add(3 * time.Hour) 20 refexp := now.Add(7 * 24 * time.Hour) ··· 50 return nil, err 51 } 52 53 + if err := s.db.Create(ctx, &models.Token{ 54 Token: accessString, 55 Did: repo.Did, 56 RefreshToken: refreshString, ··· 60 return nil, err 61 } 62 63 + if err := s.db.Create(ctx, &models.RefreshToken{ 64 Token: refreshString, 65 Did: repo.Did, 66 CreatedAt: now,
+4
server/templates/signin.html
··· 26 type="password" 27 placeholder="Password" 28 /> 29 <input name="query_params" type="hidden" value="{{ .QueryParams }}" /> 30 <button class="primary" type="submit" value="Login">Login</button> 31 </form>
··· 26 type="password" 27 placeholder="Password" 28 /> 29 + {{ if .flashes.tokenrequired }} 30 + <br /> 31 + <input name="token" id="token" placeholder="Enter your 2FA token" /> 32 + {{ end }} 33 <input name="query_params" type="hidden" value="{{ .QueryParams }}" /> 34 <button class="primary" type="submit" value="Login">Login</button> 35 </form>
+3 -3
sqlite_blockstore/sqlite_blockstore.go
··· 45 return maybeBlock, nil 46 } 47 48 - if err := bs.db.Raw("SELECT * FROM blocks WHERE did = ? AND cid = ?", nil, bs.did, cid.Bytes()).Scan(&block).Error; err != nil { 49 return nil, err 50 } 51 ··· 71 Value: block.RawData(), 72 } 73 74 - if err := bs.db.Create(&b, []clause.Expression{clause.OnConflict{ 75 Columns: []clause.Column{{Name: "did"}, {Name: "cid"}}, 76 UpdateAll: true, 77 }}).Error; err != nil { ··· 94 } 95 96 func (bs *SqliteBlockstore) PutMany(ctx context.Context, blocks []blocks.Block) error { 97 - tx := bs.db.BeginDangerously() 98 99 for _, block := range blocks { 100 bs.inserts[block.Cid()] = block
··· 45 return maybeBlock, nil 46 } 47 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 return nil, err 50 } 51 ··· 71 Value: block.RawData(), 72 } 73 74 + if err := bs.db.Create(ctx, &b, []clause.Expression{clause.OnConflict{ 75 Columns: []clause.Column{{Name: "did"}, {Name: "cid"}}, 76 UpdateAll: true, 77 }}).Error; err != nil { ··· 94 } 95 96 func (bs *SqliteBlockstore) PutMany(ctx context.Context, blocks []blocks.Block) error { 97 + tx := bs.db.BeginDangerously(ctx) 98 99 for _, block := range blocks { 100 bs.inserts[block.Cid()] = block
+1 -1
test.go
··· 32 33 u.Path = "xrpc/com.atproto.sync.subscribeRepos" 34 conn, _, err := dialer.Dial(u.String(), http.Header{ 35 - "User-Agent": []string{fmt.Sprintf("hot-topic/0.0.0")}, 36 }) 37 if err != nil { 38 return fmt.Errorf("subscribing to firehose failed (dialing): %w", err)
··· 32 33 u.Path = "xrpc/com.atproto.sync.subscribeRepos" 34 conn, _, err := dialer.Dial(u.String(), http.Header{ 35 + "User-Agent": []string{"cocoon-test/0.0.0"}, 36 }) 37 if err != nil { 38 return fmt.Errorf("subscribing to firehose failed (dialing): %w", err)