+7
cmd/cocoon/main.go
+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
+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
+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
+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
+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
+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
-1
oauth/client/manager.go
+1
-1
oauth/dpop/jti_cache.go
+1
-1
oauth/dpop/jti_cache.go
+10
-8
server/common.go
+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
+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
+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
+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
+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
+4
-2
server/handle_identity_get_recommended_did_credentials.go
+4
-2
server/handle_identity_get_recommended_did_credentials.go
···
8
)
9
10
func (s *Server) handleGetRecommendedDidCredentials(e echo.Context) error {
11
repo := e.Get("repo").(*models.RepoActor)
12
k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey)
13
if err != nil {
14
-
s.logger.Error("error parsing key", "error", err)
15
return helpers.ServerError(e, nil)
16
}
17
creds, err := s.plcClient.CreateDidCredentials(k, "", repo.Actor.Handle)
18
if err != nil {
19
-
s.logger.Error("error crating did credentials", "error", err)
20
return helpers.ServerError(e, nil)
21
}
22
···
8
)
9
10
func (s *Server) handleGetRecommendedDidCredentials(e echo.Context) error {
11
+
logger := s.logger.With("name", "handleIdentityGetRecommendedDidCredentials")
12
+
13
repo := e.Get("repo").(*models.RepoActor)
14
k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey)
15
if err != nil {
16
+
logger.Error("error parsing key", "error", err)
17
return helpers.ServerError(e, nil)
18
}
19
creds, err := s.plcClient.CreateDidCredentials(k, "", repo.Actor.Handle)
20
if err != nil {
21
+
logger.Error("error crating did credentials", "error", err)
22
return helpers.ServerError(e, nil)
23
}
24
+6
-3
server/handle_identity_request_plc_operation.go
+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
+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
+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
+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
+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
+11
-8
server/handle_oauth_par.go
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+3
-1
server/handle_repo_list_repos.go
···
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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+1
-1
server/handle_server_get_session.go
+9
-6
server/handle_server_refresh_session.go
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+3
-1
server/handle_sync_get_latest_commit.go
···
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
+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
+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
+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
+
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
+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
+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
+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
+19
server/mail.go
···
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
+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
+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
-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
+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
+4
server/templates/signin.html
···
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
+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
+1
-1
test.go