+3
.env
+3
.env
+27
.gitignore
+27
.gitignore
···
1
+
# If you prefer the allow list template instead of the deny list, see community template:
2
+
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3
+
#
4
+
# Binaries for programs and plugins
5
+
*.exe
6
+
*.exe~
7
+
*.dll
8
+
*.so
9
+
*.dylib
10
+
11
+
.vscode/
12
+
.env
13
+
14
+
# Feed Generator Binary
15
+
feedgen
16
+
17
+
# Test binary, built with `go test -c`
18
+
*.test
19
+
20
+
# Output of the go coverage tool, specifically when used with LiteIDE
21
+
*.out
22
+
23
+
# Dependency directories (remove the comment below to include it)
24
+
# vendor/
25
+
26
+
# Go workspace file
27
+
go.work
+27
Dockerfile
+27
Dockerfile
···
1
+
FROM golang:1.22 as builder
2
+
3
+
WORKDIR /app
4
+
5
+
COPY go.mod go.sum ./
6
+
7
+
RUN go mod download
8
+
9
+
COPY pkg/ pkg/
10
+
11
+
COPY cmd/ cmd/
12
+
13
+
COPY Makefile Makefile
14
+
15
+
RUN make build
16
+
17
+
FROM alpine:latest as certs
18
+
19
+
RUN apk --update add ca-certificates
20
+
21
+
FROM debian:stable-slim
22
+
23
+
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
24
+
25
+
COPY --from=builder /app/feedgen .
26
+
27
+
CMD ["./feedgen"]
+21
LICENSE
+21
LICENSE
···
1
+
MIT License
2
+
3
+
Copyright (c) 2023 Jaz
4
+
5
+
Permission is hereby granted, free of charge, to any person obtaining a copy
6
+
of this software and associated documentation files (the "Software"), to deal
7
+
in the Software without restriction, including without limitation the rights
8
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+
copies of the Software, and to permit persons to whom the Software is
10
+
furnished to do so, subject to the following conditions:
11
+
12
+
The above copyright notice and this permission notice shall be included in all
13
+
copies or substantial portions of the Software.
14
+
15
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+
SOFTWARE.
+12
Makefile
+12
Makefile
···
1
+
# Variables
2
+
ENV_FILE = .env
3
+
GO_CMD = CGO_ENABLED=1 GOOS=linux go
4
+
5
+
# Build the Feedgen Go binary
6
+
build:
7
+
@echo "Building Feed Generator Go binary..."
8
+
$(GO_CMD) build -o feedgen cmd/*.go
9
+
10
+
up:
11
+
@echo "Starting Go Feed Generator..."
12
+
docker compose -f docker-compose.yml up --build -d
+64
README.md
+64
README.md
···
1
+
# go-bsky-feed-generator
2
+
A minimal implementation of a BlueSky Feed Generator in Go
3
+
4
+
5
+
## Requirements
6
+
7
+
To run this feed generator, all you need is `docker` with `docker-compose`.
8
+
9
+
## Running
10
+
11
+
Start up the feed generator by running: `make up`
12
+
13
+
This will build the feed generator service binary inside a docker container and stand up the service on your machine at port `9032`.
14
+
15
+
To view a sample static feed (with only one post) go to:
16
+
17
+
- [`http://localhost:9032/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:replace-me-with-your-did/app.bsky.feed.generator/static`](http://localhost:9032/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:replace-me-with-your-did/app.bsky.feed.generator/static)
18
+
19
+
Update the variables in `.env` when you actually want to deploy the service somewhere, at which point `did:plc:replace-me-with-your-did` should be replaced with the value of `FEED_ACTOR_DID`.
20
+
21
+
## Accessing
22
+
23
+
This service exposes the following routes:
24
+
25
+
- `/.well-known/did.json`
26
+
- This route is used by ATProto to verify ownership of the DID the service is claiming, it's a static JSON document.
27
+
- You can see how this is generated in `pkg/gin/endpoints.go:GetWellKnownDID()`
28
+
- `/xrpc/app.bsky.feed.getFeedSkeleton`
29
+
- This route is what clients call to generate a feed page, it includes three query parameters for feed generation: `feed`, `cursor`, and `limit`
30
+
- You can see how those are parsed and handled in `pkg/gin/endpoints.go:GetFeedSkeleton()`
31
+
- `/xrpc/app.bsky.feed.describeFeedGenerator`
32
+
- This route is how the service advertises which feeds it supports to clients.
33
+
- You can see how those are parsed and handled in `pkg/gin/endpoints.go:DescribeFeeds()`
34
+
35
+
## Publishing
36
+
37
+
Once you've got your feed generator up and running and have it exposed to the internet, you can publish the feed using the script from the official BSky repo [here](https://github.com/bluesky-social/feed-generator/blob/main/scripts/publishFeedGen.ts).
38
+
39
+
Your feed will be published under _your_ DID and should show up in your profile under the `feeds` tab.
40
+
41
+
## Architecture
42
+
43
+
This repo is structured to abstract away a `Feed` interface that allows for you to add all sorts of feeds to the router.
44
+
45
+
These feeds can be simple static feeds like the `pkg/feeds/static/feed.go` implementation, or they can be much more complex feeds that draw on different data sources and filter them in cool ways to produce pages of feed items.
46
+
47
+
The `Feed` interface is defined by any struct implementing two functions:
48
+
49
+
``` go
50
+
type Feed interface {
51
+
GetPage(ctx context.Context, feed string, userDID string, limit int64, cursor string) (feedPosts []*appbsky.FeedDefs_SkeletonFeedPost, newCursor *string, err error)
52
+
Describe(ctx context.Context) ([]appbsky.FeedDescribeFeedGenerator_Feed, error)
53
+
}
54
+
```
55
+
56
+
`GetPage` gets a page of a feed for a given user with the limit and cursor provided, this is the main function that serves posts to a user.
57
+
58
+
`Describe` is used by the router to advertise what feeds are available, for foward compatibility, `Feed`s should be self describing in case this endpoint allows more details about feeds to be provided.
59
+
60
+
You can configure external resources and requirements in your Feed implementation before `Adding` the feed to the `FeedRouter` with `feedRouter.AddFeed([]string{"{feed_name}"}, feedInstance)`
61
+
62
+
This `Feed` interface is somewhat flexible right now but it could be better. I'm not sure if it will change in the future so keep that in mind when using this template.
63
+
64
+
- This has since been updated to allow a Feed to take in a feed name when generating a page and register multiple aliases for feeds that are supported.
+178
cmd/main.go
+178
cmd/main.go
···
1
+
package main
2
+
3
+
import (
4
+
"context"
5
+
"fmt"
6
+
"log"
7
+
"net/http"
8
+
"net/url"
9
+
"os"
10
+
"time"
11
+
12
+
auth "github.com/ericvolp12/go-bsky-feed-generator/pkg/auth"
13
+
"github.com/ericvolp12/go-bsky-feed-generator/pkg/feedrouter"
14
+
ginendpoints "github.com/ericvolp12/go-bsky-feed-generator/pkg/gin"
15
+
16
+
staticfeed "github.com/ericvolp12/go-bsky-feed-generator/pkg/feeds/static"
17
+
ginprometheus "github.com/ericvolp12/go-gin-prometheus"
18
+
"github.com/gin-gonic/gin"
19
+
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
20
+
"go.opentelemetry.io/otel"
21
+
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
22
+
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
23
+
"go.opentelemetry.io/otel/sdk/resource"
24
+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
25
+
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
26
+
)
27
+
28
+
func main() {
29
+
ctx := context.Background()
30
+
31
+
// Configure feed generator from environment variables
32
+
33
+
// Registers a tracer Provider globally if the exporter endpoint is set
34
+
if os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") != "" {
35
+
log.Println("initializing tracer...")
36
+
shutdown, err := installExportPipeline(ctx)
37
+
if err != nil {
38
+
log.Fatal(err)
39
+
}
40
+
defer func() {
41
+
if err := shutdown(ctx); err != nil {
42
+
log.Fatal(err)
43
+
}
44
+
}()
45
+
}
46
+
47
+
feedActorDID := os.Getenv("FEED_ACTOR_DID")
48
+
if feedActorDID == "" {
49
+
log.Fatal("FEED_ACTOR_DID environment variable must be set")
50
+
}
51
+
52
+
// serviceEndpoint is a URL that the feed generator will be available at
53
+
serviceEndpoint := os.Getenv("SERVICE_ENDPOINT")
54
+
if serviceEndpoint == "" {
55
+
log.Fatal("SERVICE_ENDPOINT environment variable must be set")
56
+
}
57
+
58
+
// Set the acceptable DIDs for the feed generator to respond to
59
+
// We'll default to the feedActorDID and the Service Endpoint as a did:web
60
+
serviceURL, err := url.Parse(serviceEndpoint)
61
+
if err != nil {
62
+
log.Fatal(fmt.Errorf("error parsing service endpoint: %w", err))
63
+
}
64
+
65
+
serviceWebDID := "did:web:" + serviceURL.Hostname()
66
+
67
+
log.Printf("service DID Web: %s", serviceWebDID)
68
+
69
+
acceptableDIDs := []string{feedActorDID, serviceWebDID}
70
+
71
+
// Create a new feed router instance
72
+
feedRouter, err := feedrouter.NewFeedRouter(ctx, feedActorDID, serviceWebDID, acceptableDIDs, serviceEndpoint)
73
+
if err != nil {
74
+
log.Fatal(fmt.Errorf("error creating feed router: %w", err))
75
+
}
76
+
77
+
// Here we can add feeds to the Feed Router instance
78
+
// Feeds conform to the Feed interface, which is defined in
79
+
// pkg/feedrouter/feedrouter.go
80
+
81
+
// For demonstration purposes, we'll use a static feed generator
82
+
// that will always return the same feed skeleton (one post)
83
+
staticFeed, staticFeedAliases, err := staticfeed.NewStaticFeed(
84
+
ctx,
85
+
feedActorDID,
86
+
"static",
87
+
// This static post is the conversation that sparked this demo repo
88
+
[]string{"at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.post/3jx7msc4ive26"},
89
+
)
90
+
91
+
// Add the static feed to the feed generator
92
+
feedRouter.AddFeed(staticFeedAliases, staticFeed)
93
+
94
+
// Create a gin router with default middleware for logging and recovery
95
+
router := gin.Default()
96
+
97
+
// Plug in OTEL Middleware and skip metrics endpoint
98
+
router.Use(
99
+
otelgin.Middleware(
100
+
"go-bsky-feed-generator",
101
+
otelgin.WithFilter(func(req *http.Request) bool {
102
+
return req.URL.Path != "/metrics"
103
+
}),
104
+
),
105
+
)
106
+
107
+
// Add Prometheus metrics middleware
108
+
p := ginprometheus.NewPrometheus("gin", nil)
109
+
p.Use(router)
110
+
111
+
// Add unauthenticated routes for feed generator
112
+
ep := ginendpoints.NewEndpoints(feedRouter)
113
+
router.GET("/.well-known/did.json", ep.GetWellKnownDID)
114
+
router.GET("/xrpc/app.bsky.feed.describeFeedGenerator", ep.DescribeFeeds)
115
+
116
+
// Plug in Authentication Middleware
117
+
auther, err := auth.NewAuth(
118
+
100_000,
119
+
time.Hour*12,
120
+
5,
121
+
serviceWebDID,
122
+
)
123
+
if err != nil {
124
+
log.Fatalf("Failed to create Auth: %v", err)
125
+
}
126
+
127
+
router.Use(auther.AuthenticateGinRequestViaJWT)
128
+
129
+
// Add authenticated routes for feed generator
130
+
router.GET("/xrpc/app.bsky.feed.getFeedSkeleton", ep.GetFeedSkeleton)
131
+
132
+
port := os.Getenv("PORT")
133
+
if port == "" {
134
+
port = "8080"
135
+
}
136
+
137
+
log.Printf("Starting server on port %s", port)
138
+
router.Run(fmt.Sprintf(":%s", port))
139
+
}
140
+
141
+
// installExportPipeline registers a trace provider instance as a global trace provider,
142
+
func installExportPipeline(ctx context.Context) (func(context.Context) error, error) {
143
+
client := otlptracehttp.NewClient()
144
+
exporter, err := otlptrace.New(ctx, client)
145
+
if err != nil {
146
+
return nil, fmt.Errorf("creating OTLP trace exporter: %w", err)
147
+
}
148
+
149
+
tracerProvider := newTraceProvider(exporter)
150
+
otel.SetTracerProvider(tracerProvider)
151
+
152
+
return tracerProvider.Shutdown, nil
153
+
}
154
+
155
+
// newTraceProvider creates a new trace provider instance.
156
+
func newTraceProvider(exp sdktrace.SpanExporter) *sdktrace.TracerProvider {
157
+
// Ensure default SDK resources and the required service name are set.
158
+
r, err := resource.Merge(
159
+
resource.Default(),
160
+
resource.NewWithAttributes(
161
+
semconv.SchemaURL,
162
+
semconv.ServiceName("go-bsky-feed-generator"),
163
+
),
164
+
)
165
+
166
+
if err != nil {
167
+
panic(err)
168
+
}
169
+
170
+
// initialize the traceIDRatioBasedSampler to sample all traces
171
+
traceIDRatioBasedSampler := sdktrace.TraceIDRatioBased(1)
172
+
173
+
return sdktrace.NewTracerProvider(
174
+
sdktrace.WithSampler(traceIDRatioBasedSampler),
175
+
sdktrace.WithBatcher(exp),
176
+
sdktrace.WithResource(r),
177
+
)
178
+
}
+16
docker-compose.yml
+16
docker-compose.yml
···
1
+
version: '3.8'
2
+
3
+
services:
4
+
go-bsky-feed-generator:
5
+
build:
6
+
dockerfile: Dockerfile
7
+
extra_hosts:
8
+
- "host.docker.internal:host-gateway"
9
+
image: go-bsky-feed-generator
10
+
container_name: go-bsky-feed-generator
11
+
environment:
12
+
- GIN_MODE=release
13
+
env_file:
14
+
- .env
15
+
ports:
16
+
- "9032:9032"
+115
go.mod
+115
go.mod
···
1
+
module github.com/ericvolp12/go-bsky-feed-generator
2
+
3
+
go 1.22
4
+
5
+
toolchain go1.22.2
6
+
7
+
require (
8
+
github.com/bluesky-social/indigo v0.0.0-20240425170844-efe2ce5ca2e1
9
+
github.com/ericvolp12/go-gin-prometheus v0.0.0-20221219081010-fc0e0436c283
10
+
github.com/ericvolp12/jwt-go-secp256k1 v0.0.2
11
+
github.com/gin-gonic/gin v1.9.1
12
+
github.com/golang-jwt/jwt v3.2.2+incompatible
13
+
github.com/hashicorp/golang-lru/arc/v2 v2.0.7
14
+
github.com/prometheus/client_golang v1.17.0
15
+
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6
16
+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b
17
+
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0
18
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1
19
+
go.opentelemetry.io/otel v1.21.0
20
+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0
21
+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0
22
+
go.opentelemetry.io/otel/sdk v1.21.0
23
+
golang.org/x/time v0.3.0
24
+
)
25
+
26
+
require (
27
+
github.com/beorn7/perks v1.0.1 // indirect
28
+
github.com/bytedance/sonic v1.9.1 // indirect
29
+
github.com/carlmjohnson/versioninfo v0.22.5 // indirect
30
+
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
31
+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
32
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
33
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
34
+
github.com/felixge/httpsnoop v1.0.4 // indirect
35
+
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
36
+
github.com/gin-contrib/sse v0.1.0 // indirect
37
+
github.com/go-logr/logr v1.4.1 // indirect
38
+
github.com/go-logr/stdr v1.2.2 // indirect
39
+
github.com/go-playground/locales v0.14.1 // indirect
40
+
github.com/go-playground/universal-translator v0.18.1 // indirect
41
+
github.com/go-playground/validator/v10 v10.14.0 // indirect
42
+
github.com/goccy/go-json v0.10.2 // indirect
43
+
github.com/gogo/protobuf v1.3.2 // indirect
44
+
github.com/golang/protobuf v1.5.3 // indirect
45
+
github.com/google/uuid v1.4.0 // indirect
46
+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect
47
+
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
48
+
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
49
+
github.com/hashicorp/golang-lru v1.0.2 // indirect
50
+
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
51
+
github.com/ipfs/bbloom v0.0.4 // indirect
52
+
github.com/ipfs/go-block-format v0.2.0 // indirect
53
+
github.com/ipfs/go-cid v0.4.1 // indirect
54
+
github.com/ipfs/go-datastore v0.6.0 // indirect
55
+
github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect
56
+
github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect
57
+
github.com/ipfs/go-ipfs-util v0.0.3 // indirect
58
+
github.com/ipfs/go-ipld-cbor v0.1.0 // indirect
59
+
github.com/ipfs/go-ipld-format v0.6.0 // indirect
60
+
github.com/ipfs/go-log v1.0.5 // indirect
61
+
github.com/ipfs/go-log/v2 v2.5.1 // indirect
62
+
github.com/ipfs/go-metrics-interface v0.0.1 // indirect
63
+
github.com/jbenet/goprocess v0.1.4 // indirect
64
+
github.com/json-iterator/go v1.1.12 // indirect
65
+
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
66
+
github.com/leodido/go-urn v1.2.4 // indirect
67
+
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
68
+
github.com/lestrrat-go/httpcc v1.0.1 // indirect
69
+
github.com/lestrrat-go/httprc v1.0.4 // indirect
70
+
github.com/lestrrat-go/iter v1.0.2 // indirect
71
+
github.com/lestrrat-go/jwx/v2 v2.0.12 // indirect
72
+
github.com/lestrrat-go/option v1.0.1 // indirect
73
+
github.com/mattn/go-isatty v0.0.20 // indirect
74
+
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
75
+
github.com/minio/sha256-simd v1.0.1 // indirect
76
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
77
+
github.com/modern-go/reflect2 v1.0.2 // indirect
78
+
github.com/mr-tron/base58 v1.2.0 // indirect
79
+
github.com/multiformats/go-base32 v0.1.0 // indirect
80
+
github.com/multiformats/go-base36 v0.2.0 // indirect
81
+
github.com/multiformats/go-multibase v0.2.0 // indirect
82
+
github.com/multiformats/go-multihash v0.2.3 // indirect
83
+
github.com/multiformats/go-varint v0.0.7 // indirect
84
+
github.com/opentracing/opentracing-go v1.2.0 // indirect
85
+
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
86
+
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect
87
+
github.com/prometheus/client_model v0.5.0 // indirect
88
+
github.com/prometheus/common v0.45.0 // indirect
89
+
github.com/prometheus/procfs v0.12.0 // indirect
90
+
github.com/segmentio/asm v1.2.0 // indirect
91
+
github.com/sirupsen/logrus v1.9.2 // indirect
92
+
github.com/spaolacci/murmur3 v1.1.0 // indirect
93
+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
94
+
github.com/ugorji/go/codec v1.2.11 // indirect
95
+
github.com/whyrusleeping/cbor-gen v0.1.1-0.20240311221002-68b9f235c302 // indirect
96
+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
97
+
go.opentelemetry.io/otel/metric v1.21.0 // indirect
98
+
go.opentelemetry.io/otel/trace v1.21.0 // indirect
99
+
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
100
+
go.uber.org/atomic v1.11.0 // indirect
101
+
go.uber.org/multierr v1.11.0 // indirect
102
+
go.uber.org/zap v1.26.0 // indirect
103
+
golang.org/x/arch v0.3.0 // indirect
104
+
golang.org/x/crypto v0.21.0 // indirect
105
+
golang.org/x/net v0.21.0 // indirect
106
+
golang.org/x/sys v0.18.0 // indirect
107
+
golang.org/x/text v0.14.0 // indirect
108
+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
109
+
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect
110
+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
111
+
google.golang.org/grpc v1.59.0 // indirect
112
+
google.golang.org/protobuf v1.31.0 // indirect
113
+
gopkg.in/yaml.v3 v3.0.1 // indirect
114
+
lukechampine.com/blake3 v1.2.1 // indirect
115
+
)
+385
go.sum
+385
go.sum
···
1
+
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2
+
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
3
+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
4
+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
5
+
github.com/bluesky-social/indigo v0.0.0-20240425170844-efe2ce5ca2e1 h1:ShHHUAk9RAMML+ectW+lbz0O7Xp0Xpn7XiYhIfCk7bA=
6
+
github.com/bluesky-social/indigo v0.0.0-20240425170844-efe2ce5ca2e1/go.mod h1:dBIOGhsiK0rgEETnxiGiuEyrSx6DKxEotHIPbiKD6WU=
7
+
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
8
+
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
9
+
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
10
+
github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc=
11
+
github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8=
12
+
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
13
+
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
14
+
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
15
+
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
16
+
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
17
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
18
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
19
+
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
20
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
22
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23
+
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
24
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
25
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
26
+
github.com/ericvolp12/go-gin-prometheus v0.0.0-20221219081010-fc0e0436c283 h1:wafHcqvqdYoSHRGetNRQ20h7N8UJiY1ZtJrU6+Ru3/E=
27
+
github.com/ericvolp12/go-gin-prometheus v0.0.0-20221219081010-fc0e0436c283/go.mod h1:+yIuLDugyQ+ho7DQ/nJnM/Jej5jb1rftWD5eMW0CVP4=
28
+
github.com/ericvolp12/jwt-go-secp256k1 v0.0.2 h1:puGwrNTY2vCt8eakkSEq2yeNxUD3zb2kPhv1OsF1hPs=
29
+
github.com/ericvolp12/jwt-go-secp256k1 v0.0.2/go.mod h1:ntxzdN7EhBp8h+N78AtN2hjbVKHa7mijryYd9nPMyMo=
30
+
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
31
+
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
32
+
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
33
+
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
34
+
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
35
+
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
36
+
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
37
+
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
38
+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
39
+
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
40
+
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
41
+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
42
+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
43
+
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
44
+
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
45
+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
46
+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
47
+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
48
+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
49
+
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
50
+
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
51
+
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
52
+
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
53
+
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
54
+
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
55
+
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
56
+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
57
+
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
58
+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
59
+
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
60
+
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
61
+
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
62
+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
63
+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
64
+
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
65
+
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
66
+
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
67
+
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
68
+
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
69
+
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
70
+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U=
71
+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y=
72
+
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
73
+
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
74
+
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
75
+
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
76
+
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
77
+
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
78
+
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
79
+
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
80
+
github.com/hashicorp/golang-lru/arc/v2 v2.0.7 h1:QxkVTxwColcduO+LP7eJO56r2hFiG8zEbfAAzRv52KQ=
81
+
github.com/hashicorp/golang-lru/arc/v2 v2.0.7/go.mod h1:Pe7gBlGdc8clY5LJ0LpJXMt5AmgmWNH1g+oFFVUHOEc=
82
+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
83
+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
84
+
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
85
+
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
86
+
github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs=
87
+
github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM=
88
+
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
89
+
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
90
+
github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk=
91
+
github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8=
92
+
github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
93
+
github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
94
+
github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ=
95
+
github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE=
96
+
github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw=
97
+
github.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo=
98
+
github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0=
99
+
github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs=
100
+
github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs=
101
+
github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk=
102
+
github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U=
103
+
github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg=
104
+
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
105
+
github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=
106
+
github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g=
107
+
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
108
+
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
109
+
github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg=
110
+
github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY=
111
+
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
112
+
github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o=
113
+
github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
114
+
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
115
+
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
116
+
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
117
+
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
118
+
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
119
+
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
120
+
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
121
+
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
122
+
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
123
+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
124
+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
125
+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
126
+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
127
+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
128
+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
129
+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
130
+
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
131
+
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
132
+
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
133
+
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
134
+
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
135
+
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
136
+
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
137
+
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
138
+
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
139
+
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
140
+
github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA=
141
+
github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ=
142
+
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
143
+
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
144
+
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
145
+
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
146
+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
147
+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
148
+
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
149
+
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
150
+
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
151
+
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
152
+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
153
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
154
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
155
+
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
156
+
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
157
+
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
158
+
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
159
+
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
160
+
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
161
+
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
162
+
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
163
+
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
164
+
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
165
+
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
166
+
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
167
+
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
168
+
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
169
+
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
170
+
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
171
+
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
172
+
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
173
+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
174
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
175
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
176
+
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0=
177
+
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
178
+
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
179
+
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
180
+
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
181
+
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
182
+
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
183
+
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
184
+
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
185
+
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
186
+
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
187
+
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
188
+
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
189
+
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
190
+
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
191
+
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
192
+
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
193
+
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
194
+
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
195
+
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
196
+
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
197
+
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
198
+
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
199
+
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
200
+
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
201
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
202
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
203
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
204
+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
205
+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
206
+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
207
+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
208
+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
209
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
210
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
211
+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
212
+
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
213
+
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
214
+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
215
+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
216
+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
217
+
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
218
+
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
219
+
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
220
+
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
221
+
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
222
+
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
223
+
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
224
+
github.com/whyrusleeping/cbor-gen v0.1.1-0.20240311221002-68b9f235c302 h1:MhInbXe4SzcImAKktUvWBCWZgcw6MYf5NfumTj1BhAw=
225
+
github.com/whyrusleeping/cbor-gen v0.1.1-0.20240311221002-68b9f235c302/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
226
+
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 h1:yJ9/LwIGIk/c0CdoavpC9RNSGSruIspSZtxG3Nnldic=
227
+
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6/go.mod h1:39U9RRVr4CKbXpXYopWn+FSH5s+vWu6+RmguSPWAq5s=
228
+
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
229
+
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
230
+
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
231
+
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
232
+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
233
+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
234
+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
235
+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
236
+
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0 h1:l7AmwSVqozWKKXeZHycpdmpycQECRpoGwJ1FW2sWfTo=
237
+
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0/go.mod h1:Ep4uoO2ijR0f49Pr7jAqyTjSCyS1SRL18wwttKfwqXA=
238
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
239
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
240
+
go.opentelemetry.io/contrib/propagators/b3 v1.17.0 h1:ImOVvHnku8jijXqkwCSyYKRDt2YrnGXD4BbhcpfbfJo=
241
+
go.opentelemetry.io/contrib/propagators/b3 v1.17.0/go.mod h1:IkfUfMpKWmynvvE0264trz0sf32NRTZL4nuAN9AbWRc=
242
+
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
243
+
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
244
+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
245
+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
246
+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM=
247
+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I=
248
+
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
249
+
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
250
+
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
251
+
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
252
+
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
253
+
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
254
+
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
255
+
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
256
+
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
257
+
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
258
+
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
259
+
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
260
+
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
261
+
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
262
+
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
263
+
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
264
+
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
265
+
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
266
+
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
267
+
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
268
+
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
269
+
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
270
+
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
271
+
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
272
+
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
273
+
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
274
+
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
275
+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
276
+
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
277
+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
278
+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
279
+
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
280
+
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
281
+
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
282
+
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
283
+
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
284
+
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
285
+
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
286
+
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
287
+
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
288
+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
289
+
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
290
+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
291
+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
292
+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
293
+
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
294
+
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
295
+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
296
+
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
297
+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
298
+
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
299
+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
300
+
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
301
+
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
302
+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
303
+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
304
+
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
305
+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
306
+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
307
+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
308
+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
309
+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
310
+
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
311
+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
312
+
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
313
+
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
314
+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
315
+
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
316
+
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
317
+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
318
+
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
319
+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
320
+
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
321
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
322
+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
323
+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
324
+
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
325
+
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
326
+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
327
+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
328
+
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
329
+
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
330
+
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
331
+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
332
+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
333
+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
334
+
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
335
+
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
336
+
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
337
+
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
338
+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
339
+
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
340
+
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
341
+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
342
+
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
343
+
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
344
+
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
345
+
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
346
+
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
347
+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
348
+
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
349
+
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
350
+
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
351
+
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
352
+
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
353
+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
354
+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
355
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
356
+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
357
+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
358
+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
359
+
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
360
+
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
361
+
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY=
362
+
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI=
363
+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
364
+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
365
+
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
366
+
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
367
+
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
368
+
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
369
+
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
370
+
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
371
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
372
+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
373
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
374
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
375
+
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
376
+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
377
+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
378
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
379
+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
380
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
381
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
382
+
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
383
+
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
384
+
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
385
+
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+211
pkg/auth/auth.go
+211
pkg/auth/auth.go
···
1
+
package auth
2
+
3
+
import (
4
+
"context"
5
+
"fmt"
6
+
"net/http"
7
+
"strings"
8
+
"time"
9
+
10
+
"github.com/bluesky-social/indigo/atproto/identity"
11
+
"github.com/bluesky-social/indigo/atproto/syntax"
12
+
es256k "github.com/ericvolp12/jwt-go-secp256k1"
13
+
"github.com/gin-gonic/gin"
14
+
"github.com/golang-jwt/jwt"
15
+
lru "github.com/hashicorp/golang-lru/arc/v2"
16
+
"github.com/prometheus/client_golang/prometheus"
17
+
"github.com/prometheus/client_golang/prometheus/promauto"
18
+
"gitlab.com/yawning/secp256k1-voi/secec"
19
+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
20
+
"go.opentelemetry.io/otel"
21
+
"go.opentelemetry.io/otel/attribute"
22
+
"golang.org/x/time/rate"
23
+
)
24
+
25
+
type KeyCacheEntry struct {
26
+
UserDID string
27
+
Key any
28
+
ExpiresAt time.Time
29
+
}
30
+
31
+
// Initialize Prometheus Metrics for cache hits and misses
32
+
var cacheHits = promauto.NewCounterVec(prometheus.CounterOpts{
33
+
Name: "feedgen_auth_cache_hits_total",
34
+
Help: "The total number of cache hits",
35
+
}, []string{"cache_type"})
36
+
37
+
var cacheMisses = promauto.NewCounterVec(prometheus.CounterOpts{
38
+
Name: "feedgen_auth_cache_misses_total",
39
+
Help: "The total number of cache misses",
40
+
}, []string{"cache_type"})
41
+
42
+
var cacheSize = promauto.NewGaugeVec(prometheus.GaugeOpts{
43
+
Name: "feedgen_auth_cache_size_bytes",
44
+
Help: "The size of the cache in bytes",
45
+
}, []string{"cache_type"})
46
+
47
+
type Auth struct {
48
+
KeyCache *lru.ARCCache[string, KeyCacheEntry]
49
+
KeyCacheTTL time.Duration
50
+
ServiceDID string
51
+
Dir *identity.CacheDirectory
52
+
}
53
+
54
+
// NewAuth creates a new Auth instance with the given key cache size and TTL
55
+
// The PLC Directory URL is also required, as well as the DID of the service
56
+
// for JWT audience validation
57
+
// The key cache is used to cache the public keys of users for a given TTL
58
+
// The PLC Directory URL is used to fetch the public keys of users
59
+
// The service DID is used to validate the audience of JWTs
60
+
// The HTTP client is used to make requests to the PLC Directory
61
+
// A rate limiter is used to limit the number of requests to the PLC Directory
62
+
func NewAuth(
63
+
keyCacheSize int,
64
+
keyCacheTTL time.Duration,
65
+
requestsPerSecond int,
66
+
serviceDID string,
67
+
) (*Auth, error) {
68
+
keyCache, err := lru.NewARC[string, KeyCacheEntry](keyCacheSize)
69
+
if err != nil {
70
+
return nil, fmt.Errorf("Failed to create key cache: %v", err)
71
+
}
72
+
73
+
// Initialize the HTTP client with OpenTelemetry instrumentation
74
+
client := http.Client{
75
+
Transport: otelhttp.NewTransport(http.DefaultTransport),
76
+
}
77
+
78
+
baseDir := identity.BaseDirectory{
79
+
PLCURL: identity.DefaultPLCURL,
80
+
PLCLimiter: rate.NewLimiter(rate.Limit(float64(requestsPerSecond)), 1),
81
+
HTTPClient: client,
82
+
TryAuthoritativeDNS: true,
83
+
// primary Bluesky PDS instance only supports HTTP resolution method
84
+
SkipDNSDomainSuffixes: []string{".bsky.social"},
85
+
}
86
+
dir := identity.NewCacheDirectory(&baseDir, keyCacheSize, keyCacheTTL, time.Minute*2, keyCacheTTL)
87
+
88
+
return &Auth{
89
+
KeyCache: keyCache,
90
+
KeyCacheTTL: keyCacheTTL,
91
+
ServiceDID: serviceDID,
92
+
Dir: &dir,
93
+
}, nil
94
+
}
95
+
96
+
func (auth *Auth) GetClaimsFromAuthHeader(ctx context.Context, authHeader string, claims jwt.Claims) error {
97
+
tracer := otel.Tracer("auth")
98
+
ctx, span := tracer.Start(ctx, "Auth:GetClaimsFromAuthHeader")
99
+
defer span.End()
100
+
101
+
if authHeader == "" {
102
+
span.End()
103
+
return fmt.Errorf("No Authorization header provided")
104
+
}
105
+
106
+
authHeaderParts := strings.Split(authHeader, " ")
107
+
if len(authHeaderParts) != 2 {
108
+
return fmt.Errorf("Invalid Authorization header")
109
+
}
110
+
111
+
if authHeaderParts[0] != "Bearer" {
112
+
return fmt.Errorf("Invalid Authorization header (expected Bearer)")
113
+
}
114
+
115
+
accessToken := authHeaderParts[1]
116
+
117
+
parser := jwt.Parser{
118
+
ValidMethods: []string{es256k.SigningMethodES256K.Alg()},
119
+
}
120
+
121
+
token, err := parser.ParseWithClaims(accessToken, claims, func(token *jwt.Token) (interface{}, error) {
122
+
if claims, ok := token.Claims.(*jwt.StandardClaims); ok {
123
+
// Get the user's key from PLC Directory
124
+
userDID := claims.Issuer
125
+
entry, ok := auth.KeyCache.Get(userDID)
126
+
if ok && entry.ExpiresAt.After(time.Now()) {
127
+
cacheHits.WithLabelValues("key").Inc()
128
+
span.SetAttributes(attribute.Bool("caches.keys.hit", true))
129
+
return entry.Key, nil
130
+
}
131
+
132
+
cacheMisses.WithLabelValues("key").Inc()
133
+
span.SetAttributes(attribute.Bool("caches.keys.hit", false))
134
+
135
+
did, err := syntax.ParseDID(userDID)
136
+
if err != nil {
137
+
return nil, fmt.Errorf("Failed to parse user DID: %v", err)
138
+
}
139
+
140
+
// Get the user's key from PLC Directory
141
+
id, err := auth.Dir.LookupDID(ctx, did)
142
+
if err != nil {
143
+
return nil, fmt.Errorf("Failed to lookup user DID: %v", err)
144
+
}
145
+
146
+
key, err := id.GetPublicKey("atproto")
147
+
if err != nil {
148
+
return nil, fmt.Errorf("Failed to get user public key: %v", err)
149
+
}
150
+
151
+
parsedPubkey, err := secec.NewPublicKey(key.UncompressedBytes())
152
+
if err != nil {
153
+
return nil, fmt.Errorf("Failed to parse user public key: %v", err)
154
+
}
155
+
156
+
// Add the ECDSA key to the cache
157
+
auth.KeyCache.Add(userDID, KeyCacheEntry{
158
+
Key: parsedPubkey,
159
+
ExpiresAt: time.Now().Add(auth.KeyCacheTTL),
160
+
})
161
+
162
+
return parsedPubkey, nil
163
+
}
164
+
165
+
return nil, fmt.Errorf("Invalid authorization token (failed to parse claims)")
166
+
})
167
+
168
+
if err != nil {
169
+
return fmt.Errorf("Failed to parse authorization token: %v", err)
170
+
}
171
+
172
+
if !token.Valid {
173
+
return fmt.Errorf("Invalid authorization token")
174
+
}
175
+
176
+
return nil
177
+
}
178
+
179
+
func (auth *Auth) AuthenticateGinRequestViaJWT(c *gin.Context) {
180
+
tracer := otel.Tracer("auth")
181
+
ctx, span := tracer.Start(c.Request.Context(), "Auth:AuthenticateGinRequestViaJWT")
182
+
183
+
authHeader := c.GetHeader("Authorization")
184
+
if authHeader == "" {
185
+
span.End()
186
+
c.Next()
187
+
return
188
+
}
189
+
190
+
claims := jwt.StandardClaims{}
191
+
192
+
err := auth.GetClaimsFromAuthHeader(ctx, authHeader, &claims)
193
+
if err != nil {
194
+
c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Errorf("Failed to get claims from auth header: %v", err).Error()})
195
+
span.End()
196
+
c.Abort()
197
+
return
198
+
}
199
+
200
+
if claims.Audience != auth.ServiceDID {
201
+
c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("Invalid audience (expected %s)", auth.ServiceDID)})
202
+
c.Abort()
203
+
return
204
+
}
205
+
206
+
// Set claims Issuer to context as user DID
207
+
c.Set("user_did", claims.Issuer)
208
+
span.SetAttributes(attribute.String("user.did", claims.Issuer))
209
+
span.End()
210
+
c.Next()
211
+
}
+101
pkg/feedrouter/feedrouter.go
+101
pkg/feedrouter/feedrouter.go
···
1
+
// Package feedrouter describes the FeedRouter type, which is responsible for generating feeds for a given DID.
2
+
// It also describes the Feed interface, which is implemented by the various feed types.
3
+
package feedrouter
4
+
5
+
import (
6
+
"context"
7
+
"fmt"
8
+
9
+
appbsky "github.com/bluesky-social/indigo/api/bsky"
10
+
did "github.com/whyrusleeping/go-did"
11
+
)
12
+
13
+
type Feed interface {
14
+
GetPage(ctx context.Context, feed string, userDID string, limit int64, cursor string) (feedPosts []*appbsky.FeedDefs_SkeletonFeedPost, newCursor *string, err error)
15
+
Describe(ctx context.Context) ([]appbsky.FeedDescribeFeedGenerator_Feed, error)
16
+
}
17
+
18
+
type FeedRouter struct {
19
+
FeedActorDID did.DID // DID of the Repo the Feed is published under
20
+
ServiceEndpoint string // URL of the FeedRouter service
21
+
ServiceDID did.DID // DID of the FeedRouter service
22
+
DIDDocument did.Document // DID Document of the FeedRouter service
23
+
AcceptableURIPrefixes []string // URIs that the FeedRouter is allowed to generate feeds for
24
+
FeedMap map[string]Feed // map of FeedName to Feed
25
+
Feeds []Feed
26
+
}
27
+
28
+
type NotFoundError struct {
29
+
error
30
+
}
31
+
32
+
// NewFeedRouter returns a new FeedRouter
33
+
func NewFeedRouter(
34
+
ctx context.Context,
35
+
feedActorDIDString string,
36
+
serviceDIDString string,
37
+
acceptableDIDs []string,
38
+
serviceEndpoint string,
39
+
) (*FeedRouter, error) {
40
+
acceptableURIPrefixes := []string{}
41
+
for _, did := range acceptableDIDs {
42
+
acceptableURIPrefixes = append(acceptableURIPrefixes, "at://"+did+"/app.bsky.feed.generator/")
43
+
}
44
+
45
+
serviceDID, err := did.ParseDID(serviceDIDString)
46
+
if err != nil {
47
+
return nil, fmt.Errorf("error parsing serviceDID: %w", err)
48
+
}
49
+
50
+
feedActorDID, err := did.ParseDID(feedActorDIDString)
51
+
if err != nil {
52
+
return nil, fmt.Errorf("error parsing feedActorDID: %w", err)
53
+
}
54
+
55
+
serviceID, err := did.ParseDID("#bsky_fg")
56
+
if err != nil {
57
+
panic(err)
58
+
}
59
+
60
+
doc := did.Document{
61
+
Context: []string{did.CtxDIDv1},
62
+
ID: serviceDID,
63
+
Service: []did.Service{
64
+
{
65
+
ID: serviceID,
66
+
Type: "BskyFeedGenerator",
67
+
ServiceEndpoint: serviceEndpoint,
68
+
},
69
+
},
70
+
}
71
+
72
+
return &FeedRouter{
73
+
FeedMap: map[string]Feed{},
74
+
FeedActorDID: feedActorDID,
75
+
ServiceDID: serviceDID,
76
+
DIDDocument: doc,
77
+
AcceptableURIPrefixes: acceptableURIPrefixes,
78
+
ServiceEndpoint: serviceEndpoint,
79
+
}, nil
80
+
}
81
+
82
+
// AddFeed adds a feed to the FeedRouter
83
+
// Feed precedence for overlapping aliases is determined by the order in which
84
+
// they are added (first added is highest precedence)
85
+
func (fg *FeedRouter) AddFeed(feedAliases []string, feed Feed) {
86
+
if fg.FeedMap == nil {
87
+
fg.FeedMap = map[string]Feed{}
88
+
}
89
+
90
+
for _, feedAlias := range feedAliases {
91
+
// Skip the feed if we already have the alias registered so we don't add it twice
92
+
// Feed precedence is determined by the order in which they are added
93
+
if _, ok := fg.FeedMap[feedAlias]; ok {
94
+
continue
95
+
}
96
+
97
+
fg.FeedMap[feedAlias] = feed
98
+
}
99
+
100
+
fg.Feeds = append(fg.Feeds, feed)
101
+
}
+79
pkg/feeds/static/feed.go
+79
pkg/feeds/static/feed.go
···
1
+
package static
2
+
3
+
import (
4
+
"context"
5
+
"fmt"
6
+
"strconv"
7
+
8
+
appbsky "github.com/bluesky-social/indigo/api/bsky"
9
+
)
10
+
11
+
type StaticFeed struct {
12
+
FeedActorDID string
13
+
FeedName string
14
+
StaticPostURIs []string
15
+
}
16
+
17
+
// NewStaticFeed returns a new StaticFeed, a list of aliases for the feed, and an error
18
+
// StaticFeed is a trivial implementation of the Feed interface, so its aliases are just the input feedName
19
+
func NewStaticFeed(ctx context.Context, feedActorDID string, feedName string, staticPostURIs []string) (*StaticFeed, []string, error) {
20
+
return &StaticFeed{
21
+
FeedActorDID: feedActorDID,
22
+
FeedName: feedName,
23
+
StaticPostURIs: staticPostURIs,
24
+
}, []string{feedName}, nil
25
+
}
26
+
27
+
// GetPage returns a list of FeedDefs_SkeletonFeedPost, a new cursor, and an error
28
+
// It takes a feed name, a user DID, a limit, and a cursor
29
+
// The feed name can be used to produce different feeds from the same feed generator
30
+
func (sf *StaticFeed) GetPage(ctx context.Context, feed string, userDID string, limit int64, cursor string) ([]*appbsky.FeedDefs_SkeletonFeedPost, *string, error) {
31
+
cursorAsInt := int64(0)
32
+
var err error
33
+
34
+
if cursor != "" {
35
+
cursorAsInt, err = strconv.ParseInt(cursor, 10, 64)
36
+
if err != nil {
37
+
return nil, nil, fmt.Errorf("cursor is not an integer: %w", err)
38
+
}
39
+
}
40
+
41
+
posts := []*appbsky.FeedDefs_SkeletonFeedPost{}
42
+
43
+
for i, postURI := range sf.StaticPostURIs {
44
+
if int64(i) < cursorAsInt {
45
+
continue
46
+
}
47
+
48
+
if int64(len(posts)) >= limit {
49
+
break
50
+
}
51
+
52
+
posts = append(posts, &appbsky.FeedDefs_SkeletonFeedPost{
53
+
Post: postURI,
54
+
})
55
+
}
56
+
57
+
cursorAsInt += int64(len(posts))
58
+
59
+
var newCursor *string
60
+
61
+
if cursorAsInt < int64(len(sf.StaticPostURIs)) {
62
+
newCursor = new(string)
63
+
*newCursor = strconv.FormatInt(cursorAsInt, 10)
64
+
}
65
+
66
+
return posts, newCursor, nil
67
+
}
68
+
69
+
// Describe returns a list of FeedDescribeFeedGenerator_Feed, and an error
70
+
// StaticFeed is a trivial implementation of the Feed interface, so it returns a single FeedDescribeFeedGenerator_Feed
71
+
// For a more complicated feed, this function would return a list of FeedDescribeFeedGenerator_Feed with the URIs of aliases
72
+
// supported by the feed
73
+
func (sf *StaticFeed) Describe(ctx context.Context) ([]appbsky.FeedDescribeFeedGenerator_Feed, error) {
74
+
return []appbsky.FeedDescribeFeedGenerator_Feed{
75
+
{
76
+
Uri: "at://" + sf.FeedActorDID + "/app.bsky.feed.generator/" + sf.FeedName,
77
+
},
78
+
}, nil
79
+
}
+173
pkg/gin/endpoints.go
+173
pkg/gin/endpoints.go
···
1
+
package gin
2
+
3
+
import (
4
+
"fmt"
5
+
"net/http"
6
+
"strconv"
7
+
"strings"
8
+
9
+
appbsky "github.com/bluesky-social/indigo/api/bsky"
10
+
"github.com/ericvolp12/go-bsky-feed-generator/pkg/feedrouter"
11
+
"github.com/gin-gonic/gin"
12
+
"github.com/whyrusleeping/go-did"
13
+
"go.opentelemetry.io/otel"
14
+
"go.opentelemetry.io/otel/attribute"
15
+
)
16
+
17
+
type Endpoints struct {
18
+
FeedRouter *feedrouter.FeedRouter
19
+
}
20
+
21
+
type DidResponse struct {
22
+
Context []string `json:"@context"`
23
+
ID string `json:"id"`
24
+
Service []did.Service `json:"service"`
25
+
}
26
+
27
+
func NewEndpoints(feedRouter *feedrouter.FeedRouter) *Endpoints {
28
+
return &Endpoints{
29
+
FeedRouter: feedRouter,
30
+
}
31
+
}
32
+
33
+
func (ep *Endpoints) GetWellKnownDID(c *gin.Context) {
34
+
tracer := otel.Tracer("feedrouter")
35
+
_, span := tracer.Start(c.Request.Context(), "GetWellKnownDID")
36
+
defer span.End()
37
+
38
+
// Use a custom struct to fix missing omitempty on did.Document
39
+
didResponse := DidResponse{
40
+
Context: ep.FeedRouter.DIDDocument.Context,
41
+
ID: ep.FeedRouter.DIDDocument.ID.String(),
42
+
Service: ep.FeedRouter.DIDDocument.Service,
43
+
}
44
+
45
+
c.JSON(http.StatusOK, didResponse)
46
+
}
47
+
48
+
func (ep *Endpoints) DescribeFeeds(c *gin.Context) {
49
+
tracer := otel.Tracer("feedrouter")
50
+
ctx, span := tracer.Start(c.Request.Context(), "DescribeFeeds")
51
+
defer span.End()
52
+
53
+
feedDescriptions := []*appbsky.FeedDescribeFeedGenerator_Feed{}
54
+
55
+
for _, feed := range ep.FeedRouter.Feeds {
56
+
newDescriptions, err := feed.Describe(ctx)
57
+
if err != nil {
58
+
span.RecordError(err)
59
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
60
+
return
61
+
}
62
+
63
+
for _, newDescription := range newDescriptions {
64
+
description := newDescription
65
+
feedDescriptions = append(feedDescriptions, &description)
66
+
}
67
+
}
68
+
69
+
span.SetAttributes(attribute.Int("feeds.length", len(feedDescriptions)))
70
+
71
+
feedGeneratorDescription := appbsky.FeedDescribeFeedGenerator_Output{
72
+
Did: ep.FeedRouter.FeedActorDID.String(),
73
+
Feeds: feedDescriptions,
74
+
}
75
+
76
+
c.JSON(http.StatusOK, feedGeneratorDescription)
77
+
}
78
+
79
+
func (ep *Endpoints) GetFeedSkeleton(c *gin.Context) {
80
+
// Incoming requests should have a query parameter "feed" that looks like:
81
+
// at://did:web:feedsky.jazco.io/app.bsky.feed.generator/feed-name
82
+
// Also a query parameter "limit" that looks like: 50
83
+
// Also a query parameter "cursor" that is either the empty string
84
+
// or the cursor returned from a previous request
85
+
tracer := otel.Tracer("feed-generator")
86
+
ctx, span := tracer.Start(c.Request.Context(), "FeedGenerator:GetFeedSkeleton")
87
+
defer span.End()
88
+
89
+
// Get userDID from the request context, which is set by the auth middleware
90
+
userDID := c.GetString("user_did")
91
+
92
+
feedQuery := c.Query("feed")
93
+
if feedQuery == "" {
94
+
c.JSON(http.StatusBadRequest, gin.H{"error": "feed query parameter is required"})
95
+
return
96
+
}
97
+
98
+
c.Set("feedQuery", feedQuery)
99
+
span.SetAttributes(attribute.String("feed.query", feedQuery))
100
+
101
+
feedPrefix := ""
102
+
for _, acceptablePrefix := range ep.FeedRouter.AcceptableURIPrefixes {
103
+
if strings.HasPrefix(feedQuery, acceptablePrefix) {
104
+
feedPrefix = acceptablePrefix
105
+
break
106
+
}
107
+
}
108
+
109
+
if feedPrefix == "" {
110
+
c.JSON(http.StatusBadRequest, gin.H{"error": "this feed generator does not serve feeds for the given DID"})
111
+
return
112
+
}
113
+
114
+
// Get the feed name from the query
115
+
feedName := strings.TrimPrefix(feedQuery, feedPrefix)
116
+
if feedName == "" {
117
+
c.JSON(http.StatusBadRequest, gin.H{"error": "feed name is required"})
118
+
return
119
+
}
120
+
121
+
span.SetAttributes(attribute.String("feed.name", feedName))
122
+
c.Set("feedName", feedName)
123
+
124
+
// Get the limit from the query, default to 50, maximum of 250
125
+
limit := int64(50)
126
+
limitQuery := c.Query("limit")
127
+
span.SetAttributes(attribute.String("feed.limit.raw", limitQuery))
128
+
if limitQuery != "" {
129
+
parsedLimit, err := strconv.ParseInt(limitQuery, 10, 64)
130
+
if err != nil {
131
+
span.SetAttributes(attribute.Bool("feed.limit.failed_to_parse", true))
132
+
limit = 50
133
+
} else {
134
+
limit = parsedLimit
135
+
if limit > 250 {
136
+
span.SetAttributes(attribute.Bool("feed.limit.clamped", true))
137
+
limit = 250
138
+
}
139
+
}
140
+
}
141
+
142
+
span.SetAttributes(attribute.Int64("feed.limit.parsed", limit))
143
+
144
+
// Get the cursor from the query
145
+
cursor := c.Query("cursor")
146
+
c.Set("cursor", cursor)
147
+
148
+
if ep.FeedRouter.FeedMap == nil {
149
+
c.JSON(http.StatusInternalServerError, gin.H{"error": "feed generator has no feeds configured"})
150
+
return
151
+
}
152
+
153
+
feed, ok := ep.FeedRouter.FeedMap[feedName]
154
+
if !ok {
155
+
c.JSON(http.StatusNotFound, gin.H{"error": "feed not found"})
156
+
return
157
+
}
158
+
159
+
// Get the feed items
160
+
feedItems, newCursor, err := feed.GetPage(ctx, feedName, userDID, limit, cursor)
161
+
if err != nil {
162
+
span.RecordError(err)
163
+
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get feed items: %s", err.Error())})
164
+
return
165
+
}
166
+
167
+
span.SetAttributes(attribute.Int("feed.items.length", len(feedItems)))
168
+
169
+
c.JSON(http.StatusOK, appbsky.FeedGetFeedSkeleton_Output{
170
+
Feed: feedItems,
171
+
Cursor: newCursor,
172
+
})
173
+
}