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