An atproto PDS written in Go

Compare changes

Choose any two refs to compare.

+3 -3
go.mod
··· 25 25 github.com/joho/godotenv v1.5.1 26 26 github.com/labstack/echo-contrib v0.17.4 27 27 github.com/labstack/echo/v4 v4.13.3 28 - github.com/lestrrat-go/jwx/v2 v2.0.12 28 + github.com/lestrrat-go/jwx/v2 v2.0.21 29 29 github.com/multiformats/go-multihash v0.2.3 30 30 github.com/prometheus/client_golang v1.23.2 31 31 github.com/samber/slog-echo v1.16.1 ··· 88 88 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 89 89 github.com/labstack/gommon v0.4.2 // indirect 90 90 github.com/leodido/go-urn v1.4.0 // indirect 91 - github.com/lestrrat-go/blackmagic v1.0.1 // indirect 91 + github.com/lestrrat-go/blackmagic v1.0.2 // indirect 92 92 github.com/lestrrat-go/httpcc v1.0.1 // indirect 93 - github.com/lestrrat-go/httprc v1.0.4 // indirect 93 + github.com/lestrrat-go/httprc v1.0.5 // indirect 94 94 github.com/lestrrat-go/iter v1.0.2 // indirect 95 95 github.com/lestrrat-go/option v1.0.1 // indirect 96 96 github.com/mattn/go-colorable v0.1.14 // indirect
+6 -39
go.sum
··· 36 36 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 37 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 38 38 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 39 - github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 40 39 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 41 40 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 42 41 github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= ··· 222 221 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 223 222 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 224 223 github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 225 - github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= 226 - 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= 227 226 github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= 228 227 github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= 229 - github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= 230 - 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= 231 230 github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= 232 231 github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= 233 - github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA= 234 - github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ= 235 - 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= 236 234 github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= 237 235 github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 238 236 github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= ··· 325 323 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 326 324 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 327 325 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 328 - github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 329 - github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 330 326 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 331 327 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 332 328 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 333 329 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 334 330 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 335 - github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 336 - github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 337 331 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 338 332 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 339 333 github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= ··· 356 350 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 357 351 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 358 352 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 359 - github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 360 353 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 361 354 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 362 355 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= ··· 391 384 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 392 385 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 393 386 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 394 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 395 - golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 396 387 golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= 397 388 golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 398 389 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= ··· 402 393 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 403 394 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 404 395 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 405 - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 406 - golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 407 396 golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= 408 397 golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= 409 398 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= ··· 411 400 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 412 401 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 413 402 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 414 - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 415 403 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 416 - golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 417 - golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 418 - golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 419 404 golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= 420 405 golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 421 406 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 422 407 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 423 408 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 424 409 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 425 - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 426 - golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 427 410 golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 428 411 golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 429 412 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= ··· 432 415 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 433 416 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 434 417 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 435 - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 436 418 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 437 - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 438 - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 439 - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 440 419 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 441 420 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 442 - golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 443 - golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 444 421 golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 445 422 golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 446 423 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 447 - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 448 - golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 449 - golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 450 - golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 451 424 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 452 425 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 453 - golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 454 - golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 455 - golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 456 - golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 457 426 golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= 458 427 golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= 459 428 golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= ··· 468 437 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 469 438 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 470 439 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 471 - golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 472 - golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 473 440 golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= 474 441 golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= 475 442 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+3 -21
internal/db/db.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "sync" 6 5 7 6 "gorm.io/gorm" 8 7 "gorm.io/gorm/clause" ··· 10 9 11 10 type DB struct { 12 11 cli *gorm.DB 13 - mu sync.Mutex 14 12 } 15 13 16 14 func NewDB(cli *gorm.DB) *DB { 17 15 return &DB{ 18 16 cli: cli, 19 - mu: sync.Mutex{}, 20 17 } 21 18 } 22 19 23 20 func (db *DB) Create(ctx context.Context, value any, clauses []clause.Expression) *gorm.DB { 24 - db.mu.Lock() 25 - defer db.mu.Unlock() 26 21 return db.cli.WithContext(ctx).Clauses(clauses...).Create(value) 27 22 } 28 23 29 24 func (db *DB) Save(ctx context.Context, value any, clauses []clause.Expression) *gorm.DB { 30 - db.mu.Lock() 31 - defer db.mu.Unlock() 32 25 return db.cli.WithContext(ctx).Clauses(clauses...).Save(value) 33 26 } 34 27 35 28 func (db *DB) Exec(ctx context.Context, sql string, clauses []clause.Expression, values ...any) *gorm.DB { 36 - db.mu.Lock() 37 - defer db.mu.Unlock() 38 29 return db.cli.WithContext(ctx).Clauses(clauses...).Exec(sql, values...) 39 30 } 40 31 ··· 47 38 } 48 39 49 40 func (db *DB) Delete(ctx context.Context, value any, clauses []clause.Expression) *gorm.DB { 50 - db.mu.Lock() 51 - defer db.mu.Unlock() 52 41 return db.cli.WithContext(ctx).Clauses(clauses...).Delete(value) 53 42 } 54 43 ··· 56 45 return db.cli.WithContext(ctx).First(dest, conds...) 57 46 } 58 47 59 - // TODO: this isn't actually good. we can commit even if the db is locked here. this is probably okay for the time being, but need to figure 60 - // out a better solution. right now we only do this whenever we're importing a repo though so i'm mostly not worried, but it's still bad. 61 - // e.g. when we do apply writes we should also be using a transcation but we don't right now 62 - func (db *DB) BeginDangerously(ctx context.Context) *gorm.DB { 48 + func (db *DB) Begin(ctx context.Context) *gorm.DB { 63 49 return db.cli.WithContext(ctx).Begin() 64 50 } 65 51 66 - func (db *DB) Lock() { 67 - db.mu.Lock() 68 - } 69 - 70 - func (db *DB) Unlock() { 71 - db.mu.Unlock() 52 + func (db *DB) Client() *gorm.DB { 53 + return db.cli 72 54 }
+4 -4
oauth/dpop/manager.go
··· 75 75 } 76 76 77 77 proof := extractProof(headers) 78 - 79 78 if proof == "" { 80 79 return nil, nil 81 80 } ··· 197 196 198 197 nonce, _ := claims["nonce"].(string) 199 198 if nonce == "" { 200 - // WARN: this _must_ be `use_dpop_nonce` for clients know they should make another request 199 + // reference impl checks if self.nonce is not null before returning an error, but we always have a 200 + // nonce so we do not bother checking 201 201 return nil, ErrUseDpopNonce 202 202 } 203 203 204 204 if nonce != "" && !dm.nonce.Check(nonce) { 205 - // WARN: this _must_ be `use_dpop_nonce` so that clients will fetch a new nonce 205 + // dpop nonce mismatch 206 206 return nil, ErrUseDpopNonce 207 207 } 208 208 ··· 237 237 } 238 238 239 239 func extractProof(headers http.Header) string { 240 - dpopHeaders := headers["Dpop"] 240 + dpopHeaders := headers.Values("dpop") 241 241 switch len(dpopHeaders) { 242 242 case 0: 243 243 return ""
+3 -3
oauth/provider/client_auth.go
··· 19 19 } 20 20 21 21 type AuthenticateClientRequestBase struct { 22 - ClientID string `form:"client_id" json:"client_id" validate:"required"` 23 - ClientAssertionType *string `form:"client_assertion_type" json:"client_assertion_type,omitempty"` 24 - ClientAssertion *string `form:"client_assertion" json:"client_assertion,omitempty"` 22 + ClientID string `form:"client_id" json:"client_id" query:"client_id" validate:"required"` 23 + ClientAssertionType *string `form:"client_assertion_type" json:"client_assertion_type,omitempty" query:"client_assertion_type"` 24 + ClientAssertion *string `form:"client_assertion" json:"client_assertion,omitempty" query:"client_assertion"` 25 25 } 26 26 27 27 func (p *Provider) AuthenticateClient(ctx context.Context, req AuthenticateClientRequestBase, proof *dpop.Proof, opts *AuthenticateClientOptions) (*client.Client, *ClientAuth, error) {
+9 -8
oauth/provider/models.go
··· 32 32 33 33 type ParRequest struct { 34 34 AuthenticateClientRequestBase 35 - ResponseType string `form:"response_type" json:"response_type" validate:"required"` 36 - CodeChallenge *string `form:"code_challenge" json:"code_challenge" validate:"required"` 37 - CodeChallengeMethod string `form:"code_challenge_method" json:"code_challenge_method" validate:"required"` 38 - State string `form:"state" json:"state" validate:"required"` 39 - RedirectURI string `form:"redirect_uri" json:"redirect_uri" validate:"required"` 40 - Scope string `form:"scope" json:"scope" validate:"required"` 41 - LoginHint *string `form:"login_hint" json:"login_hint,omitempty"` 42 - DpopJkt *string `form:"dpop_jkt" json:"dpop_jkt,omitempty"` 35 + ResponseType string `form:"response_type" json:"response_type" query:"response_type" validate:"required"` 36 + CodeChallenge *string `form:"code_challenge" json:"code_challenge" query:"code_challenge" validate:"required"` 37 + CodeChallengeMethod string `form:"code_challenge_method" json:"code_challenge_method" query:"code_challenge_method" validate:"required"` 38 + State string `form:"state" json:"state" query:"state" validate:"required"` 39 + RedirectURI string `form:"redirect_uri" json:"redirect_uri" query:"redirect_uri" validate:"required"` 40 + Scope string `form:"scope" json:"scope" query:"scope" validate:"required"` 41 + LoginHint *string `form:"login_hint" query:"login_hint" json:"login_hint,omitempty"` 42 + DpopJkt *string `form:"dpop_jkt" query:"dpop_jkt" json:"dpop_jkt,omitempty"` 43 + ResponseMode *string `form:"response_mode" json:"response_mode,omitempty" query:"response_mode"` 43 44 } 44 45 45 46 func (opr *ParRequest) Scan(value any) error {
+1 -1
server/handle_import_repo.go
··· 66 66 return helpers.ServerError(e, nil) 67 67 } 68 68 69 - tx := s.db.BeginDangerously(ctx) 69 + tx := s.db.Begin(ctx) 70 70 71 71 clock := syntax.NewTIDClock(0) 72 72
+95 -19
server/handle_oauth_authorize.go
··· 1 1 package server 2 2 3 3 import ( 4 + "fmt" 4 5 "net/url" 5 6 "strings" 6 7 "time" ··· 8 9 "github.com/Azure/go-autorest/autorest/to" 9 10 "github.com/haileyok/cocoon/internal/helpers" 10 11 "github.com/haileyok/cocoon/oauth" 12 + "github.com/haileyok/cocoon/oauth/constants" 11 13 "github.com/haileyok/cocoon/oauth/provider" 12 14 "github.com/labstack/echo/v4" 13 15 ) 14 16 17 + type HandleOauthAuthorizeGetInput struct { 18 + RequestUri string `query:"request_uri"` 19 + } 20 + 15 21 func (s *Server) handleOauthAuthorizeGet(e echo.Context) error { 16 22 ctx := e.Request().Context() 17 23 18 - reqUri := e.QueryParam("request_uri") 19 - if reqUri == "" { 20 - // render page for logged out dev 21 - if s.config.Version == "dev" { 22 - return e.Render(200, "authorize.html", map[string]any{ 23 - "Scopes": []string{"atproto", "transition:generic"}, 24 - "AppName": "DEV MODE AUTHORIZATION PAGE", 25 - "Handle": "paula.cocoon.social", 26 - "RequestUri": "", 27 - }) 24 + logger := s.logger.With("name", "handleOauthAuthorizeGet") 25 + 26 + var input HandleOauthAuthorizeGetInput 27 + if err := e.Bind(&input); err != nil { 28 + logger.Error("error binding request", "err", err) 29 + return fmt.Errorf("error binding request") 30 + } 31 + 32 + var reqId string 33 + if input.RequestUri != "" { 34 + id, err := oauth.DecodeRequestUri(input.RequestUri) 35 + if err != nil { 36 + logger.Error("no request uri found in input", "url", e.Request().URL.String()) 37 + return helpers.InputError(e, to.StringPtr("no request uri")) 38 + } 39 + reqId = id 40 + } else { 41 + var parRequest provider.ParRequest 42 + if err := e.Bind(&parRequest); err != nil { 43 + s.logger.Error("error binding for standard auth request", "error", err) 44 + return helpers.InputError(e, to.StringPtr("InvalidRequest")) 45 + } 46 + 47 + if err := e.Validate(parRequest); err != nil { 48 + // render page for logged out dev 49 + if s.config.Version == "dev" && parRequest.ClientID == "" { 50 + return e.Render(200, "authorize.html", map[string]any{ 51 + "Scopes": []string{"atproto", "transition:generic"}, 52 + "AppName": "DEV MODE AUTHORIZATION PAGE", 53 + "Handle": "paula.cocoon.social", 54 + "RequestUri": "", 55 + }) 56 + } 57 + return helpers.InputError(e, to.StringPtr("no request uri and invalid parameters")) 28 58 } 29 - return helpers.InputError(e, to.StringPtr("no request uri")) 59 + 60 + client, clientAuth, err := s.oauthProvider.AuthenticateClient(ctx, parRequest.AuthenticateClientRequestBase, nil, &provider.AuthenticateClientOptions{ 61 + AllowMissingDpopProof: true, 62 + }) 63 + if err != nil { 64 + s.logger.Error("error authenticating client in standard request", "client_id", parRequest.ClientID, "error", err) 65 + return helpers.ServerError(e, to.StringPtr(err.Error())) 66 + } 67 + 68 + if parRequest.DpopJkt == nil { 69 + if client.Metadata.DpopBoundAccessTokens { 70 + } 71 + } else { 72 + if !client.Metadata.DpopBoundAccessTokens { 73 + msg := "dpop bound access tokens are not enabled for this client" 74 + return helpers.InputError(e, &msg) 75 + } 76 + } 77 + 78 + eat := time.Now().Add(constants.ParExpiresIn) 79 + id := oauth.GenerateRequestId() 80 + 81 + authRequest := &provider.OauthAuthorizationRequest{ 82 + RequestId: id, 83 + ClientId: client.Metadata.ClientID, 84 + ClientAuth: *clientAuth, 85 + Parameters: parRequest, 86 + ExpiresAt: eat, 87 + } 88 + 89 + if err := s.db.Create(ctx, authRequest, nil).Error; err != nil { 90 + s.logger.Error("error creating auth request in db", "error", err) 91 + return helpers.ServerError(e, nil) 92 + } 93 + 94 + input.RequestUri = oauth.EncodeRequestUri(id) 95 + reqId = id 96 + 30 97 } 31 98 32 99 repo, _, err := s.getSessionRepoOrErr(e) 33 100 if err != nil { 34 101 return e.Redirect(303, "/account/signin?"+e.QueryParams().Encode()) 35 - } 36 - 37 - reqId, err := oauth.DecodeRequestUri(reqUri) 38 - if err != nil { 39 - return helpers.InputError(e, to.StringPtr(err.Error())) 40 102 } 41 103 42 104 var req provider.OauthAuthorizationRequest ··· 60 122 data := map[string]any{ 61 123 "Scopes": scopes, 62 124 "AppName": appName, 63 - "RequestUri": reqUri, 125 + "RequestUri": input.RequestUri, 64 126 "QueryParams": e.QueryParams().Encode(), 65 127 "Handle": repo.Actor.Handle, 66 128 } ··· 129 191 q.Set("code", code) 130 192 131 193 hashOrQuestion := "?" 132 - if authReq.ClientAuth.Method != "private_key_jwt" { 133 - hashOrQuestion = "#" 194 + if authReq.Parameters.ResponseMode != nil { 195 + switch *authReq.Parameters.ResponseMode { 196 + case "fragment": 197 + hashOrQuestion = "#" 198 + case "query": 199 + // do nothing 200 + break 201 + default: 202 + if authReq.Parameters.ResponseType != "code" { 203 + hashOrQuestion = "#" 204 + } 205 + } 206 + } else { 207 + if authReq.Parameters.ResponseType != "code" { 208 + hashOrQuestion = "#" 209 + } 134 210 } 135 211 136 212 return e.Redirect(303, authReq.Parameters.RedirectURI+hashOrQuestion+q.Encode())
+1
server/handle_oauth_par.go
··· 42 42 e.Response().Header().Set("DPoP-Nonce", nonce) 43 43 e.Response().Header().Add("access-control-expose-headers", "DPoP-Nonce") 44 44 } 45 + logger.Error("nonce error: use_dpop_nonce", "headers", e.Request().Header) 45 46 return e.JSON(400, map[string]string{ 46 47 "error": "use_dpop_nonce", 47 48 })
+1 -1
server/handle_server_delete_account.go
··· 69 69 }) 70 70 } 71 71 72 - tx := s.db.BeginDangerously(ctx) 72 + tx := s.db.Begin(ctx) 73 73 if tx.Error != nil { 74 74 logger.Error("error starting transaction", "error", tx.Error) 75 75 return helpers.ServerError(e, nil)
+28 -8
server/handle_sync_subscribe_repos.go
··· 12 12 ) 13 13 14 14 func (s *Server) handleSyncSubscribeRepos(e echo.Context) error { 15 - ctx := e.Request().Context() 15 + ctx, cancel := context.WithCancel(e.Request().Context()) 16 + defer cancel() 17 + 16 18 logger := s.logger.With("component", "subscribe-repos-websocket") 17 19 18 20 conn, err := websocket.Upgrade(e.Response().Writer, e.Request(), e.Response().Header(), 1<<10, 1<<10) ··· 30 32 metrics.RelaysConnected.WithLabelValues(ident).Dec() 31 33 }() 32 34 33 - evts, cancel, err := s.evtman.Subscribe(ctx, ident, func(evt *events.XRPCStreamEvent) bool { 35 + evts, evtManCancel, err := s.evtman.Subscribe(ctx, ident, func(evt *events.XRPCStreamEvent) bool { 34 36 return true 35 37 }, nil) 36 38 if err != nil { 37 39 return err 38 40 } 39 - 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 + }() 40 58 41 59 header := events.EventHeader{Op: events.EvtKindMessage} 42 60 for evt := range evts { ··· 97 115 98 116 // we should tell the relay to request a new crawl at this point if we got disconnected 99 117 // use a new context since the old one might be cancelled at this point 100 - ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) 101 - defer cancel() 102 - if err := s.requestCrawl(ctx); err != nil { 103 - logger.Error("error requesting crawls", "err", err) 104 - } 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 + }() 105 125 106 126 return nil 107 127 }
+40 -50
server/server.go
··· 322 322 if err != nil { 323 323 return nil, fmt.Errorf("failed to open sqlite database: %w", err) 324 324 } 325 + gdb.Exec("PRAGMA journal_mode=WAL") 326 + gdb.Exec("PRAGMA synchronous=NORMAL") 327 + 325 328 logger.Info("connected to SQLite database", "path", args.DbName) 326 329 } 327 330 dbw := db.NewDB(gdb) ··· 625 628 626 629 logger.Info("beginning backup to s3...") 627 630 628 - var buf bytes.Buffer 629 - if err := func() error { 630 - logger.Info("reading database bytes...") 631 - s.db.Lock() 632 - defer s.db.Unlock() 633 - 634 - sf, err := os.Open(s.dbName) 635 - if err != nil { 636 - return fmt.Errorf("error opening database for backup: %w", err) 637 - } 638 - defer sf.Close() 631 + tmpFile := fmt.Sprintf("/tmp/cocoon-backup-%s.db", time.Now().Format(time.RFC3339Nano)) 632 + defer os.Remove(tmpFile) 639 633 640 - if _, err := io.Copy(&buf, sf); err != nil { 641 - return fmt.Errorf("error reading bytes of backup db: %w", err) 642 - } 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 + } 643 638 644 - return nil 645 - }(); err != nil { 646 - 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) 647 642 return 648 643 } 649 644 650 - if err := func() error { 651 - logger.Info("sending to s3...") 645 + logger.Info("sending to s3...") 652 646 653 - currTime := time.Now().Format("2006-01-02_15-04-05") 654 - key := "cocoon-backup-" + currTime + ".db" 647 + currTime := time.Now().Format("2006-01-02_15-04-05") 648 + key := "cocoon-backup-" + currTime + ".db" 655 649 656 - config := &aws.Config{ 657 - Region: aws.String(s.s3Config.Region), 658 - Credentials: credentials.NewStaticCredentials(s.s3Config.AccessKey, s.s3Config.SecretKey, ""), 659 - } 650 + config := &aws.Config{ 651 + Region: aws.String(s.s3Config.Region), 652 + Credentials: credentials.NewStaticCredentials(s.s3Config.AccessKey, s.s3Config.SecretKey, ""), 653 + } 660 654 661 - if s.s3Config.Endpoint != "" { 662 - config.Endpoint = aws.String(s.s3Config.Endpoint) 663 - config.S3ForcePathStyle = aws.Bool(true) 664 - } 655 + if s.s3Config.Endpoint != "" { 656 + config.Endpoint = aws.String(s.s3Config.Endpoint) 657 + config.S3ForcePathStyle = aws.Bool(true) 658 + } 665 659 666 - sess, err := session.NewSession(config) 667 - if err != nil { 668 - return err 669 - } 660 + sess, err := session.NewSession(config) 661 + if err != nil { 662 + logger.Error("error creating s3 session", "err", err) 663 + return 664 + } 670 665 671 - svc := s3.New(sess) 666 + svc := s3.New(sess) 672 667 673 - if _, err := svc.PutObject(&s3.PutObjectInput{ 674 - Bucket: aws.String(s.s3Config.Bucket), 675 - Key: aws.String(key), 676 - Body: bytes.NewReader(buf.Bytes()), 677 - }); err != nil { 678 - return fmt.Errorf("error uploading file to s3: %w", err) 679 - } 680 - 681 - logger.Info("finished uploading backup to s3", "key", key, "duration", time.Now().Sub(start).Seconds()) 682 - 683 - return nil 684 - }(); err != nil { 685 - logger.Error("error uploading database backup", "error", err) 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) 686 674 return 687 675 } 688 676 689 - 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) 690 680 } 691 681 692 682 func (s *Server) backupRoutine() { ··· 721 711 if err != nil { 722 712 shouldBackupNow = true 723 713 } else { 724 - lastBackup, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", string(lastBackupStr)) 714 + lastBackup, err := time.Parse(time.RFC3339Nano, string(lastBackupStr)) 725 715 if err != nil { 726 716 shouldBackupNow = true 727 - } else if time.Now().Sub(lastBackup).Seconds() > 3600 { 717 + } else if time.Since(lastBackup).Seconds() > 3600 { 728 718 shouldBackupNow = true 729 719 } 730 720 }
+1 -1
sqlite_blockstore/sqlite_blockstore.go
··· 94 94 } 95 95 96 96 func (bs *SqliteBlockstore) PutMany(ctx context.Context, blocks []blocks.Block) error { 97 - tx := bs.db.BeginDangerously(ctx) 97 + tx := bs.db.Begin(ctx) 98 98 99 99 for _, block := range blocks { 100 100 bs.inserts[block.Cid()] = block