Vow, uncensorable PDS written in Go

refactor: fix lint + configure tangled ci

+282 -307
-116
.github/workflows/docker-image.yml
··· 1 - name: Docker image 2 - 3 - on: 4 - workflow_dispatch: 5 - push: 6 - branches: 7 - - main 8 - tags: 9 - - 'v*' 10 - 11 - env: 12 - REGISTRY: ghcr.io 13 - IMAGE_NAME: ${{ github.repository }} 14 - 15 - jobs: 16 - build-and-push-image: 17 - strategy: 18 - matrix: 19 - include: 20 - - arch: amd64 21 - runner: ubuntu-latest 22 - - arch: arm64 23 - runner: ubuntu-24.04-arm 24 - runs-on: ${{ matrix.runner }} 25 - # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. 26 - permissions: 27 - contents: read 28 - packages: write 29 - attestations: write 30 - id-token: write 31 - outputs: 32 - digest-amd64: ${{ matrix.arch == 'amd64' && steps.push.outputs.digest || '' }} 33 - digest-arm64: ${{ matrix.arch == 'arm64' && steps.push.outputs.digest || '' }} 34 - steps: 35 - - name: Checkout repository 36 - uses: actions/checkout@v4 37 - 38 - # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. 39 - - name: Log in to the Container registry 40 - uses: docker/login-action@v3 41 - with: 42 - registry: ${{ env.REGISTRY }} 43 - username: ${{ github.actor }} 44 - password: ${{ secrets.GITHUB_TOKEN }} 45 - 46 - # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. 47 - - name: Extract metadata (tags, labels) for Docker 48 - id: meta 49 - uses: docker/metadata-action@v5 50 - with: 51 - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 52 - tags: | 53 - type=raw,value=latest,enable={{is_default_branch}},suffix=-${{ matrix.arch }} 54 - type=sha,suffix=-${{ matrix.arch }} 55 - type=sha,format=long,suffix=-${{ matrix.arch }} 56 - type=semver,pattern={{version}},suffix=-${{ matrix.arch }} 57 - type=semver,pattern={{major}}.{{minor}},suffix=-${{ matrix.arch }} 58 - 59 - # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. 60 - # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. 61 - # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. 62 - - name: Build and push Docker image 63 - id: push 64 - uses: docker/build-push-action@v6 65 - with: 66 - context: . 67 - push: true 68 - tags: ${{ steps.meta.outputs.tags }} 69 - labels: ${{ steps.meta.outputs.labels }} 70 - 71 - publish-manifest: 72 - needs: build-and-push-image 73 - runs-on: ubuntu-latest 74 - permissions: 75 - packages: write 76 - attestations: write 77 - id-token: write 78 - steps: 79 - - name: Log in to the Container registry 80 - uses: docker/login-action@v3 81 - with: 82 - registry: ${{ env.REGISTRY }} 83 - username: ${{ github.actor }} 84 - password: ${{ secrets.GITHUB_TOKEN }} 85 - 86 - - name: Extract metadata (tags, labels) for Docker 87 - id: meta 88 - uses: docker/metadata-action@v5 89 - with: 90 - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 91 - tags: | 92 - type=raw,value=latest,enable={{is_default_branch}} 93 - type=sha 94 - type=sha,format=long 95 - type=semver,pattern={{version}} 96 - type=semver,pattern={{major}}.{{minor}} 97 - 98 - - name: Create and push manifest 99 - run: | 100 - # Split tags into an array 101 - readarray -t tags <<< "${{ steps.meta.outputs.tags }}" 102 - 103 - # Create and push manifest for each tag 104 - for tag in "${tags[@]}"; do 105 - docker buildx imagetools create -t "$tag" \ 106 - "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build-and-push-image.outputs.digest-amd64 }}" \ 107 - "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build-and-push-image.outputs.digest-arm64 }}" 108 - done 109 - 110 - # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)." 111 - - name: Generate artifact attestation 112 - uses: actions/attest-build-provenance@v1 113 - with: 114 - subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} 115 - subject-digest: ${{ needs.build-and-push-image.outputs.digest-amd64 }} 116 - push-to-registry: true
+24
.tangled/workflows/ci.yml
··· 1 + when: 2 + - event: ["push"] 3 + branch: ["main"] 4 + - event: ["pull_request"] 5 + branch: ["main"] 6 + 7 + engine: "nixery" 8 + 9 + # using the default values 10 + clone: 11 + skip: false 12 + depth: 1 13 + 14 + dependencies: 15 + nixpkgs: 16 + - go 17 + - gcc 18 + 19 + steps: 20 + - name: "Build servmon" 21 + command: "go build ." 22 + 23 + - name: "Run tests" 24 + command: "go test ./... -v"
+7 -1
Makefile
··· 64 64 .PHONY: lint 65 65 lint: ## Verify code style and run static checks 66 66 go vet ./... 67 - test -z $(gofmt -l ./...) 67 + go fix ./... 68 + test -z "$(shell gofmt -l ./...)" 69 + golangci-lint run ./... --fix 70 + 71 + .PHONY: lint-install 72 + lint-install: ## Install golangci-lint 73 + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest 68 74 69 75 .PHONY: fmt 70 76 fmt: ## Run syntax re-formatting (modify in place)
+10 -12
identity/identity.go
··· 48 48 if err != nil { 49 49 return "", fmt.Errorf("handle could not be resolved via web: %w", err) 50 50 } 51 - defer resp.Body.Close() 51 + defer func() { _ = resp.Body.Close() }() 52 52 53 53 b, err := io.ReadAll(resp.Body) 54 54 if err != nil { ··· 118 118 if err != nil { 119 119 return nil, err 120 120 } 121 - defer resp.Body.Close() 121 + defer func() { _ = resp.Body.Close() }() 122 122 123 123 if resp.StatusCode != 200 { 124 - io.Copy(io.Discard, resp.Body) 124 + _, _ = io.Copy(io.Discard, resp.Body) 125 125 return nil, fmt.Errorf("unable to find did doc at url. did: %s. url: %s", did, ustr) 126 126 } 127 127 ··· 138 138 cli = util.RobustHTTPClient() 139 139 } 140 140 141 - var ustr string 142 - ustr = fmt.Sprintf("https://plc.directory/%s/data", did) 141 + var ustr = fmt.Sprintf("https://plc.directory/%s/data", did) 143 142 144 143 req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil) 145 144 if err != nil { ··· 150 149 if err != nil { 151 150 return nil, err 152 151 } 153 - defer resp.Body.Close() 152 + defer func() { _ = resp.Body.Close() }() 154 153 155 154 if resp.StatusCode != 200 { 156 - io.Copy(io.Discard, resp.Body) 155 + _, _ = io.Copy(io.Discard, resp.Body) 157 156 return nil, fmt.Errorf("could not find identity in plc registry") 158 157 } 159 158 ··· 170 169 cli = util.RobustHTTPClient() 171 170 } 172 171 173 - var ustr string 174 - ustr = fmt.Sprintf("https://plc.directory/%s/log/audit", did) 172 + var ustr = fmt.Sprintf("https://plc.directory/%s/log/audit", did) 175 173 176 174 req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil) 177 175 if err != nil { 178 176 return nil, err 179 177 } 180 178 181 - resp, err := http.DefaultClient.Do(req) 179 + resp, err := cli.Do(req) 182 180 if err != nil { 183 181 return nil, err 184 182 } 185 - defer resp.Body.Close() 183 + defer func() { _ = resp.Body.Close() }() 186 184 187 185 if resp.StatusCode != 200 { 188 - io.Copy(io.Discard, resp.Body) 186 + _, _ = io.Copy(io.Discard, resp.Body) 189 187 return nil, fmt.Errorf("could not find identity in plc registry") 190 188 } 191 189
+9 -4
identity/passport.go
··· 6 6 "sync" 7 7 ) 8 8 9 + type contextKey string 10 + 11 + // SkipCacheKey is the context key used to bypass the passport cache. 12 + const SkipCacheKey contextKey = "skip-cache" 13 + 9 14 type BackingCache interface { 10 15 GetDoc(did string) (*DidDoc, bool) 11 16 PutDoc(did string, doc *DidDoc) error ··· 34 39 } 35 40 36 41 func (p *Passport) FetchDoc(ctx context.Context, did string) (*DidDoc, error) { 37 - skipCache, _ := ctx.Value("skip-cache").(bool) 42 + skipCache, _ := ctx.Value(SkipCacheKey).(bool) 38 43 39 44 if !skipCache { 40 45 p.mu.RLock() ··· 52 57 } 53 58 54 59 p.mu.Lock() 55 - p.bc.PutDoc(did, doc) 60 + _ = p.bc.PutDoc(did, doc) 56 61 p.mu.Unlock() 57 62 58 63 return doc, nil 59 64 } 60 65 61 66 func (p *Passport) ResolveHandle(ctx context.Context, handle string) (string, error) { 62 - skipCache, _ := ctx.Value("skip-cache").(bool) 67 + skipCache, _ := ctx.Value(SkipCacheKey).(bool) 63 68 64 69 if !skipCache { 65 70 p.mu.RLock() ··· 77 82 } 78 83 79 84 p.mu.Lock() 80 - p.bc.PutDid(handle, did) 85 + _ = p.bc.PutDid(handle, did) 81 86 p.mu.Unlock() 82 87 83 88 return did, nil
+17 -17
internal/helpers/helpers.go
··· 16 16 // /^[A-Z2-7]{5}-[A-Z2-7]{5}$/ 17 17 var letters = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567") 18 18 19 - func writeJSON(w http.ResponseWriter, status int, v any) error { 19 + func writeJSON(w http.ResponseWriter, status int, v any) { 20 20 w.Header().Set("Content-Type", "application/json") 21 21 w.WriteHeader(status) 22 - return json.NewEncoder(w).Encode(v) 22 + _ = json.NewEncoder(w).Encode(v) 23 23 } 24 24 25 - func InputError(w http.ResponseWriter, custom *string) error { 25 + func InputError(w http.ResponseWriter, custom *string) { 26 26 msg := "InvalidRequest" 27 27 if custom != nil { 28 28 msg = *custom 29 29 } 30 - return genericError(w, http.StatusBadRequest, msg) 30 + genericError(w, http.StatusBadRequest, msg) 31 31 } 32 32 33 - func ServerError(w http.ResponseWriter, suffix *string) error { 33 + func ServerError(w http.ResponseWriter, suffix *string) { 34 34 msg := "Internal server error" 35 35 if suffix != nil { 36 36 msg += ". " + *suffix 37 37 } 38 - return genericError(w, http.StatusInternalServerError, msg) 38 + genericError(w, http.StatusInternalServerError, msg) 39 39 } 40 40 41 - func UnauthorizedError(w http.ResponseWriter, suffix *string) error { 41 + func UnauthorizedError(w http.ResponseWriter, suffix *string) { 42 42 msg := "Unauthorized" 43 43 if suffix != nil { 44 44 msg += ". " + *suffix 45 45 } 46 - return genericError(w, http.StatusUnauthorized, msg) 46 + genericError(w, http.StatusUnauthorized, msg) 47 47 } 48 48 49 - func ForbiddenError(w http.ResponseWriter, suffix *string) error { 49 + func ForbiddenError(w http.ResponseWriter, suffix *string) { 50 50 msg := "Forbidden" 51 51 if suffix != nil { 52 52 msg += ". " + *suffix 53 53 } 54 - return genericError(w, http.StatusForbidden, msg) 54 + genericError(w, http.StatusForbidden, msg) 55 55 } 56 56 57 - func InvalidTokenError(w http.ResponseWriter) error { 57 + func InvalidTokenError(w http.ResponseWriter) { 58 58 s := "InvalidToken" 59 - return InputError(w, &s) 59 + InputError(w, &s) 60 60 } 61 61 62 - func ExpiredTokenError(w http.ResponseWriter) error { 62 + func ExpiredTokenError(w http.ResponseWriter) { 63 63 // WARN: See https://github.com/bluesky-social/atproto/discussions/3319 64 - return writeJSON(w, http.StatusBadRequest, map[string]string{ 64 + writeJSON(w, http.StatusBadRequest, map[string]string{ 65 65 "error": "ExpiredToken", 66 66 "message": "*", 67 67 }) 68 68 } 69 69 70 - func genericError(w http.ResponseWriter, code int, msg string) error { 71 - return writeJSON(w, code, map[string]string{ 70 + func genericError(w http.ResponseWriter, code int, msg string) { 71 + writeJSON(w, code, map[string]string{ 72 72 "error": msg, 73 73 }) 74 74 } ··· 91 91 92 92 func RandomBytes(n int) []byte { 93 93 bs := make([]byte, n) 94 - crand.Read(bs) 94 + _, _ = crand.Read(bs) 95 95 return bs 96 96 } 97 97
+5 -9
oauth/client/manager.go
··· 14 14 "time" 15 15 16 16 cache "github.com/go-pkgz/expirable-cache/v3" 17 - "pkg.rbrt.fr/vow/internal/helpers" 18 17 "github.com/lestrrat-go/jwx/v2/jwk" 18 + "pkg.rbrt.fr/vow/internal/helpers" 19 19 ) 20 20 21 21 type Manager struct { ··· 102 102 if err != nil { 103 103 return nil, err 104 104 } 105 - defer resp.Body.Close() 105 + defer func() { _ = resp.Body.Close() }() 106 106 107 107 if resp.StatusCode != http.StatusOK { 108 - io.Copy(io.Discard, resp.Body) 108 + _, _ = io.Copy(io.Discard, resp.Body) 109 109 return nil, fmt.Errorf("fetching client metadata returned response code %d", resp.StatusCode) 110 110 } 111 111 ··· 139 139 if err != nil { 140 140 return nil, err 141 141 } 142 - defer resp.Body.Close() 142 + defer func() { _ = resp.Body.Close() }() 143 143 144 144 if resp.StatusCode != http.StatusOK { 145 - io.Copy(io.Discard, resp.Body) 145 + _, _ = io.Copy(io.Discard, resp.Body) 146 146 return nil, fmt.Errorf("fetching client jwks returned response code %d", resp.StatusCode) 147 147 } 148 148 ··· 358 358 case u.Hostname() == "127.0.0.1", u.Hostname() == "[::1]": 359 359 if metadata.ApplicationType != "native" { 360 360 return nil, errors.New("loopback redirect uris are only allowed for native apps") 361 - } 362 - 363 - if u.Port() != "" { 364 - // reference impl doesn't do anything with this? 365 361 } 366 362 367 363 if u.Scheme != "http" {
+2 -4
plc/client.go
··· 97 97 if recovery != "" { 98 98 rotationKeys = func(recovery string) []string { 99 99 newRotationKeys := []string{recovery} 100 - for _, k := range rotationKeys { 101 - newRotationKeys = append(newRotationKeys, k) 102 - } 100 + newRotationKeys = append(newRotationKeys, rotationKeys...) 103 101 return newRotationKeys 104 102 }(recovery) 105 103 } ··· 156 154 if err != nil { 157 155 return err 158 156 } 159 - defer resp.Body.Close() 157 + defer func() { _ = resp.Body.Close() }() 160 158 161 159 b, err = io.ReadAll(resp.Body) 162 160 if err != nil {
+3 -3
recording_blockstore/recording_blockstore.go
··· 6 6 7 7 blockformat "github.com/ipfs/go-block-format" 8 8 "github.com/ipfs/go-cid" 9 - blockstore "github.com/ipfs/go-ipfs-blockstore" 9 + blockstore "github.com/ipfs/go-ipfs-blockstore" //nolint:staticcheck 10 10 ) 11 11 12 12 type RecordingBlockstore struct { 13 - base blockstore.Blockstore 13 + base blockstore.Blockstore //nolint:staticcheck 14 14 15 15 inserts map[cid.Cid]blockformat.Block 16 16 reads map[cid.Cid]blockformat.Block 17 17 } 18 18 19 - func New(base blockstore.Blockstore) *RecordingBlockstore { 19 + func New(base blockstore.Blockstore) *RecordingBlockstore { //nolint:staticcheck 20 20 return &RecordingBlockstore{ 21 21 base: base, 22 22 inserts: make(map[cid.Cid]blockformat.Block),
+2 -2
server/blockstore_variant.go
··· 1 1 package server 2 2 3 3 import ( 4 + blockstore "github.com/ipfs/go-ipfs-blockstore" //nolint:staticcheck 4 5 "pkg.rbrt.fr/vow/sqlite_blockstore" 5 - blockstore "github.com/ipfs/go-ipfs-blockstore" 6 6 ) 7 7 8 8 type BlockstoreVariant int ··· 20 20 } 21 21 } 22 22 23 - func (s *Server) getBlockstore(did string) blockstore.Blockstore { 23 + func (s *Server) getBlockstore(did string) blockstore.Blockstore { //nolint:staticcheck 24 24 switch s.config.BlockstoreVariant { 25 25 case BlockstoreVariantSqlite: 26 26 return sqlite_blockstore.New(did, s.db)
+15 -18
server/handle_account.go
··· 4 4 "net/http" 5 5 "time" 6 6 7 + "github.com/hako/durafmt" 7 8 "pkg.rbrt.fr/vow/oauth" 8 9 "pkg.rbrt.fr/vow/oauth/constants" 9 10 "pkg.rbrt.fr/vow/oauth/provider" 10 - "github.com/hako/durafmt" 11 11 ) 12 12 13 13 func (s *Server) handleAccount(w http.ResponseWriter, r *http.Request) { ··· 16 16 17 17 repo, sess, err := s.getSessionRepoOrErr(r) 18 18 if err != nil { 19 - http.Redirect(w, r, "/account/signin", 303) 19 + http.Redirect(w, r, "/account/signin", http.StatusSeeOther) 20 20 return 21 21 } 22 22 ··· 26 26 if err := s.db.Raw(ctx, "SELECT * FROM oauth_tokens WHERE sub = ? AND created_at < ? ORDER BY created_at ASC", nil, repo.Repo.Did, oldestPossibleSession).Scan(&tokens).Error; err != nil { 27 27 logger.Error("couldnt fetch oauth sessions for account", "did", repo.Repo.Did, "error", err) 28 28 sess.AddFlash("Unable to fetch sessions. See server logs for more details.", "error") 29 - sess.Save(r, w) 30 - s.renderTemplate(w, "account.html", map[string]any{ 31 - "flashes": getFlashesFromSession(w, r, sess), 32 - }) 29 + if err := sess.Save(r, w); err != nil { 30 + logger.Error("failed to save session", "error", err) 31 + } 32 + if err := s.renderTemplate(w, "account.html", map[string]any{ 33 + "flashes": s.getFlashesFromSession(w, r, sess), 34 + }); err != nil { 35 + logger.Error("failed to render template", "error", err) 36 + } 33 37 return 34 38 } 35 39 36 - var filtered []provider.OauthToken 37 - for _, t := range tokens { 38 - ageRes := oauth.GetSessionAgeFromToken(t) 39 - if ageRes.SessionExpired { 40 - continue 41 - } 42 - filtered = append(filtered, t) 43 - } 44 - 45 40 now := time.Now() 46 41 47 42 tokenInfo := []map[string]string{} ··· 70 65 }) 71 66 } 72 67 73 - s.renderTemplate(w, "account.html", map[string]any{ 68 + if err := s.renderTemplate(w, "account.html", map[string]any{ 74 69 "Repo": repo, 75 70 "Tokens": tokenInfo, 76 - "flashes": getFlashesFromSession(w, r, sess), 77 - }) 71 + "flashes": s.getFlashesFromSession(w, r, sess), 72 + }); err != nil { 73 + logger.Error("failed to render template", "error", err) 74 + } 78 75 }
+9 -5
server/handle_account_revoke.go
··· 26 26 27 27 repo, sess, err := s.getSessionRepoOrErr(r) 28 28 if err != nil { 29 - http.Redirect(w, r, "/account/signin", 303) 29 + http.Redirect(w, r, "/account/signin", http.StatusSeeOther) 30 30 return 31 31 } 32 32 33 33 if err := s.db.Exec(ctx, "DELETE FROM oauth_tokens WHERE sub = ? AND token = ?", nil, repo.Repo.Did, req.Token).Error; err != nil { 34 34 logger.Error("couldnt delete oauth session for account", "did", repo.Repo.Did, "token", req.Token, "error", err) 35 35 sess.AddFlash("Unable to revoke session. See server logs for more details.", "error") 36 - sess.Save(r, w) 37 - http.Redirect(w, r, "/account", 303) 36 + if err := sess.Save(r, w); err != nil { 37 + logger.Error("failed to save session", "error", err) 38 + } 39 + http.Redirect(w, r, "/account", http.StatusSeeOther) 38 40 return 39 41 } 40 42 41 43 sess.AddFlash("Session successfully revoked!", "success") 42 - sess.Save(r, w) 43 - http.Redirect(w, r, "/account", 303) 44 + if err := sess.Save(r, w); err != nil { 45 + logger.Error("failed to save session", "error", err) 46 + } 47 + http.Redirect(w, r, "/account", http.StatusSeeOther) 44 48 }
+40 -22
server/handle_account_signin.go
··· 9 9 10 10 "github.com/bluesky-social/indigo/atproto/syntax" 11 11 "github.com/gorilla/sessions" 12 + "golang.org/x/crypto/bcrypt" 13 + "gorm.io/gorm" 12 14 "pkg.rbrt.fr/vow/internal/helpers" 13 15 "pkg.rbrt.fr/vow/models" 14 - "golang.org/x/crypto/bcrypt" 15 - "gorm.io/gorm" 16 16 ) 17 17 18 18 type OauthSigninInput struct { ··· 43 43 return repo, sess, nil 44 44 } 45 45 46 - func getFlashesFromSession(w http.ResponseWriter, r *http.Request, sess *sessions.Session) map[string]any { 47 - defer sess.Save(r, w) 46 + func (s *Server) getFlashesFromSession(w http.ResponseWriter, r *http.Request, sess *sessions.Session) map[string]any { 47 + defer func() { 48 + if err := sess.Save(r, w); err != nil { 49 + s.logger.Error("failed to save session", "error", err) 50 + } 51 + }() 48 52 return map[string]any{ 49 53 "errors": sess.Flashes("error"), 50 54 "successes": sess.Flashes("success"), ··· 55 59 func (s *Server) handleAccountSigninGet(w http.ResponseWriter, r *http.Request) { 56 60 _, sess, err := s.getSessionRepoOrErr(r) 57 61 if err == nil { 58 - http.Redirect(w, r, "/account", 303) 62 + http.Redirect(w, r, "/account", http.StatusSeeOther) 59 63 return 60 64 } 61 65 62 - s.renderTemplate(w, "signin.html", map[string]any{ 63 - "flashes": getFlashesFromSession(w, r, sess), 66 + if err := s.renderTemplate(w, "signin.html", map[string]any{ 67 + "flashes": s.getFlashesFromSession(w, r, sess), 64 68 "QueryParams": r.URL.Query().Encode(), 65 - }) 69 + }); err != nil { 70 + s.logger.Error("failed to render template", "error", err) 71 + } 66 72 } 67 73 68 74 func (s *Server) handleAccountSigninPost(w http.ResponseWriter, r *http.Request) { ··· 116 122 } else { 117 123 sess.AddFlash("Something went wrong!", "error") 118 124 } 119 - sess.Save(r, w) 120 - http.Redirect(w, r, "/account/signin"+queryParams, 303) 125 + if err := sess.Save(r, w); err != nil { 126 + logger.Error("failed to save session", "error", err) 127 + } 128 + http.Redirect(w, r, "/account/signin"+queryParams, http.StatusSeeOther) 121 129 return 122 130 } 123 131 ··· 127 135 } else { 128 136 sess.AddFlash("Something went wrong!", "error") 129 137 } 130 - sess.Save(r, w) 131 - http.Redirect(w, r, "/account/signin"+queryParams, 303) 138 + if err := sess.Save(r, w); err != nil { 139 + logger.Error("failed to save session", "error", err) 140 + } 141 + http.Redirect(w, r, "/account/signin"+queryParams, http.StatusSeeOther) 132 142 return 133 143 } 134 144 ··· 137 147 err = s.createAndSendTwoFactorCode(ctx, repo) 138 148 if err != nil { 139 149 sess.AddFlash("Something went wrong!", "error") 140 - sess.Save(r, w) 141 - http.Redirect(w, r, "/account/signin"+queryParams, 303) 150 + if err := sess.Save(r, w); err != nil { 151 + logger.Error("failed to save session", "error", err) 152 + } 153 + http.Redirect(w, r, "/account/signin"+queryParams, http.StatusSeeOther) 142 154 return 143 155 } 144 156 145 157 sess.AddFlash("requires 2FA token", "tokenrequired") 146 - sess.Save(r, w) 147 - http.Redirect(w, r, "/account/signin"+queryParams, 303) 158 + if err := sess.Save(r, w); err != nil { 159 + logger.Error("failed to save session", "error", err) 160 + } 161 + http.Redirect(w, r, "/account/signin"+queryParams, http.StatusSeeOther) 148 162 return 149 163 } 150 164 ··· 154 168 err = s.createAndSendTwoFactorCode(ctx, repo) 155 169 if err != nil { 156 170 sess.AddFlash("Something went wrong!", "error") 157 - sess.Save(r, w) 158 - http.Redirect(w, r, "/account/signin"+queryParams, 303) 171 + if err := sess.Save(r, w); err != nil { 172 + logger.Error("failed to save session", "error", err) 173 + } 174 + http.Redirect(w, r, "/account/signin"+queryParams, http.StatusSeeOther) 159 175 return 160 176 } 161 177 162 178 sess.AddFlash("requires 2FA token", "tokenrequired") 163 - sess.Save(r, w) 164 - http.Redirect(w, r, "/account/signin"+queryParams, 303) 179 + if err := sess.Save(r, w); err != nil { 180 + logger.Error("failed to save session", "error", err) 181 + } 182 + http.Redirect(w, r, "/account/signin"+queryParams, http.StatusSeeOther) 165 183 return 166 184 } 167 185 ··· 191 209 } 192 210 193 211 if queryParams != "" { 194 - http.Redirect(w, r, "/oauth/authorize"+queryParams, 303) 212 + http.Redirect(w, r, "/oauth/authorize"+queryParams, http.StatusSeeOther) 195 213 } else { 196 - http.Redirect(w, r, "/account", 303) 214 + http.Redirect(w, r, "/account", http.StatusSeeOther) 197 215 } 198 216 }
+1 -1
server/handle_account_signout.go
··· 33 33 redirect += "?" + r.URL.Query().Encode() 34 34 } 35 35 36 - http.Redirect(w, r, redirect, 303) 36 + http.Redirect(w, r, redirect, http.StatusSeeOther) 37 37 }
+1 -1
server/handle_identity_sign_plc_operation.go
··· 59 59 return 60 60 } 61 61 62 - ctx := context.WithValue(r.Context(), "skip-cache", true) 62 + ctx := context.WithValue(r.Context(), identity.SkipCacheKey, true) 63 63 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did) 64 64 if err != nil { 65 65 logger.Error("error fetching doc", "error", err)
+5 -3
server/handle_identity_submit_plc_operation.go
··· 51 51 helpers.ServerError(w, nil) 52 52 return 53 53 } 54 - required, err := s.plcClient.CreateDidCredentials(k, "", repo.Actor.Handle) 54 + required, err := s.plcClient.CreateDidCredentials(k, "", repo.Handle) 55 55 if err != nil { 56 56 logger.Error("error creating did credentials", "error", err) 57 57 helpers.ServerError(w, nil) ··· 90 90 logger.Warn("error busting did doc", "error", err) 91 91 } 92 92 93 - s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 93 + if err := s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 94 94 RepoIdentity: &atproto.SyncSubscribeRepos_Identity{ 95 95 Did: repo.Repo.Did, 96 96 Seq: time.Now().UnixMicro(), // TODO: no 97 97 Time: time.Now().Format(util.ISO8601), 98 98 }, 99 - }) 99 + }); err != nil { 100 + s.logger.Error("failed to add event", "error", err) 101 + } 100 102 }
+5 -3
server/handle_identity_update_handle.go
··· 41 41 return 42 42 } 43 43 44 - ctx := context.WithValue(r.Context(), "skip-cache", true) 44 + ctx := context.WithValue(r.Context(), identity.SkipCacheKey, true) 45 45 46 46 if strings.HasPrefix(repo.Repo.Did, "did:plc:") { 47 47 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did) ··· 94 94 logger.Warn("error busting did doc", "error", err) 95 95 } 96 96 97 - s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 97 + if err := s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 98 98 RepoIdentity: &atproto.SyncSubscribeRepos_Identity{ 99 99 Did: repo.Repo.Did, 100 100 Handle: to.StringPtr(req.Handle), 101 101 Seq: time.Now().UnixMicro(), // TODO: no 102 102 Time: time.Now().Format(util.ISO8601), 103 103 }, 104 - }) 104 + }); err != nil { 105 + s.logger.Error("failed to add event", "error", err) 106 + } 105 107 106 108 if err := s.db.Exec(ctx, "UPDATE actors SET handle = ? WHERE did = ?", nil, req.Handle, repo.Repo.Did).Error; err != nil { 107 109 logger.Error("error updating handle in db", "error", err)
+13 -13
server/handle_oauth_authorize.go
··· 61 61 if err := s.validator.Struct(parRequest); err != nil { 62 62 // render page for logged out dev 63 63 if s.config.Version == "dev" && parRequest.ClientID == "" { 64 - s.renderTemplate(w, "authorize.html", map[string]any{ 64 + if err := s.renderTemplate(w, "authorize.html", map[string]any{ 65 65 "Scopes": []string{"atproto", "transition:generic"}, 66 66 "AppName": "DEV MODE AUTHORIZATION PAGE", 67 67 "Handle": "paula.rbrt.fr", 68 68 "RequestUri": "", 69 - }) 69 + }); err != nil { 70 + logger.Error("failed to render template", "error", err) 71 + } 70 72 return 71 73 } 72 74 helpers.InputError(w, to.StringPtr("no request uri and invalid parameters")) ··· 82 84 return 83 85 } 84 86 85 - if parRequest.DpopJkt == nil { 86 - if client.Metadata.DpopBoundAccessTokens { 87 - // nothing to do 88 - } 89 - } else { 87 + if parRequest.DpopJkt != nil { 90 88 if !client.Metadata.DpopBoundAccessTokens { 91 89 msg := "dpop bound access tokens are not enabled for this client" 92 90 helpers.InputError(w, &msg) ··· 117 115 118 116 repo, _, err := s.getSessionRepoOrErr(r) 119 117 if err != nil { 120 - http.Redirect(w, r, "/account/signin?"+r.URL.Query().Encode(), 303) 118 + http.Redirect(w, r, "/account/signin?"+r.URL.Query().Encode(), http.StatusSeeOther) 121 119 return 122 120 } 123 121 ··· 147 145 "AppName": appName, 148 146 "RequestUri": requestUri, 149 147 "QueryParams": r.URL.Query().Encode(), 150 - "Handle": repo.Actor.Handle, 148 + "Handle": repo.Handle, 151 149 } 152 150 153 - s.renderTemplate(w, "authorize.html", data) 151 + if err := s.renderTemplate(w, "authorize.html", data); err != nil { 152 + logger.Error("failed to render template", "error", err) 153 + } 154 154 } 155 155 156 156 type OauthAuthorizePostRequest struct { ··· 164 164 165 165 repo, _, err := s.getSessionRepoOrErr(r) 166 166 if err != nil { 167 - http.Redirect(w, r, "/account/signin", 303) 167 + http.Redirect(w, r, "/account/signin", http.StatusSeeOther) 168 168 return 169 169 } 170 170 ··· 199 199 200 200 // TODO: figure out how im supposed to actually redirect 201 201 if req.AcceptOrRejct == "reject" { 202 - http.Redirect(w, r, client.Metadata.ClientURI, 303) 202 + http.Redirect(w, r, client.Metadata.ClientURI, http.StatusSeeOther) 203 203 return 204 204 } 205 205 ··· 251 251 } 252 252 253 253 _ = fmt.Sprintf // avoid unused import if fmt ends up unused 254 - http.Redirect(w, r, authReq.Parameters.RedirectURI+hashOrQuestion+q.Encode(), 303) 254 + http.Redirect(w, r, authReq.Parameters.RedirectURI+hashOrQuestion+q.Encode(), http.StatusSeeOther) 255 255 }
+2 -2
server/handle_oauth_token.go
··· 234 234 RefreshToken: refreshToken, 235 235 TokenType: tokenType, 236 236 Scope: authReq.Parameters.Scope, 237 - ExpiresIn: int64(eat.Sub(time.Now()).Seconds()), 237 + ExpiresIn: int64(time.Until(eat).Seconds()), 238 238 Sub: repo.Repo.Did, 239 239 }) 240 240 return ··· 330 330 RefreshToken: nextRefreshToken, 331 331 TokenType: tokenType, 332 332 Scope: oauthToken.Parameters.Scope, 333 - ExpiresIn: int64(eat.Sub(time.Now()).Seconds()), 333 + ExpiresIn: int64(time.Until(eat).Seconds()), 334 334 Sub: oauthToken.Sub, 335 335 }) 336 336 return
+4 -2
server/handle_proxy.go
··· 168 168 helpers.ServerError(w, nil) 169 169 return 170 170 } 171 - defer resp.Body.Close() 171 + defer func() { _ = resp.Body.Close() }() 172 172 173 173 for k, v := range resp.Header { 174 174 w.Header().Set(k, strings.Join(v, ",")) 175 175 } 176 176 w.WriteHeader(resp.StatusCode) 177 - io.Copy(w, resp.Body) 177 + if _, err := io.Copy(w, resp.Body); err != nil { 178 + logger.Error("failed to copy response body", "error", err) 179 + } 178 180 }
+1 -1
server/handle_repo_upload_blob.go
··· 179 179 if err != nil { 180 180 return cid.Undef, fmt.Errorf("error calling ipfs add: %w", err) 181 181 } 182 - defer resp.Body.Close() 182 + defer func() { _ = resp.Body.Close() }() 183 183 184 184 if resp.StatusCode != http.StatusOK { 185 185 msg, _ := io.ReadAll(resp.Body)
+1 -1
server/handle_robots.go
··· 7 7 8 8 func (s *Server) handleRobots(w http.ResponseWriter, r *http.Request) { 9 9 w.Header().Set("Content-Type", "text/plain") 10 - fmt.Fprint(w, "# Beep boop beep boop\n\n# Crawl me 🥺\nUser-agent: *\nAllow: /") 10 + _, _ = fmt.Fprint(w, "# Beep boop beep boop\n\n# Crawl me 🥺\nUser-agent: *\nAllow: /") 11 11 }
+1 -1
server/handle_root.go
··· 7 7 8 8 func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) { 9 9 w.Header().Set("Content-Type", "text/plain") 10 - fmt.Fprint(w, ` 10 + _, _ = fmt.Fprint(w, ` 11 11 12 12 ....-*%%%##### 13 13 .%#+++****#%%%%%%%%%#+:....
+4 -2
server/handle_server_activate_account.go
··· 29 29 return 30 30 } 31 31 32 - s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 32 + if err := s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 33 33 RepoAccount: &atproto.SyncSubscribeRepos_Account{ 34 34 Active: true, 35 35 Did: urepo.Repo.Did, ··· 37 37 Seq: time.Now().UnixMicro(), // TODO: bad puppy 38 38 Time: time.Now().Format(util.ISO8601), 39 39 }, 40 - }) 40 + }); err != nil { 41 + s.logger.Error("failed to add event", "error", err) 42 + } 41 43 42 44 w.WriteHeader(http.StatusOK) 43 45 }
+6 -4
server/handle_server_create_account.go
··· 17 17 "github.com/bluesky-social/indigo/atproto/syntax" 18 18 "github.com/bluesky-social/indigo/events" 19 19 "github.com/bluesky-social/indigo/util" 20 + "golang.org/x/crypto/bcrypt" 21 + "gorm.io/gorm" 20 22 "pkg.rbrt.fr/vow/internal/helpers" 21 23 "pkg.rbrt.fr/vow/models" 22 - "golang.org/x/crypto/bcrypt" 23 - "gorm.io/gorm" 24 24 ) 25 25 26 26 type ComAtprotoServerCreateAccountRequest struct { ··· 268 268 return 269 269 } 270 270 271 - s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 271 + if err := s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 272 272 RepoIdentity: &atproto.SyncSubscribeRepos_Identity{ 273 273 Did: urepo.Did, 274 274 Handle: to.StringPtr(request.Handle), 275 275 Seq: time.Now().UnixMicro(), // TODO: no 276 276 Time: time.Now().Format(util.ISO8601), 277 277 }, 278 - }) 278 + }); err != nil { 279 + logger.Error("failed to add event", "error", err) 280 + } 279 281 } 280 282 281 283 if s.config.RequireInvite {
+4 -2
server/handle_server_deactivate_account.go
··· 30 30 return 31 31 } 32 32 33 - s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 33 + if err := s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 34 34 RepoAccount: &atproto.SyncSubscribeRepos_Account{ 35 35 Active: false, 36 36 Did: urepo.Repo.Did, ··· 38 38 Seq: time.Now().UnixMicro(), // TODO: bad puppy 39 39 Time: time.Now().Format(util.ISO8601), 40 40 }, 41 - }) 41 + }); err != nil { 42 + s.logger.Error("failed to add event", "error", err) 43 + } 42 44 43 45 w.WriteHeader(http.StatusOK) 44 46 }
+9 -7
server/handle_server_delete_account.go
··· 10 10 "github.com/bluesky-social/indigo/api/atproto" 11 11 "github.com/bluesky-social/indigo/events" 12 12 "github.com/bluesky-social/indigo/util" 13 - "pkg.rbrt.fr/vow/internal/helpers" 14 13 "golang.org/x/crypto/bcrypt" 14 + "pkg.rbrt.fr/vow/internal/helpers" 15 15 ) 16 16 17 17 type ComAtprotoServerDeleteAccountRequest struct { ··· 44 44 return 45 45 } 46 46 47 - if err := bcrypt.CompareHashAndPassword([]byte(urepo.Repo.Password), []byte(req.Password)); err != nil { 47 + if err := bcrypt.CompareHashAndPassword([]byte(urepo.Password), []byte(req.Password)); err != nil { 48 48 logger.Error("password mismatch", "error", err) 49 49 s.writeJSON(w, 401, map[string]string{"error": "Invalid did or password"}) 50 50 return 51 51 } 52 52 53 - if urepo.Repo.AccountDeleteCode == nil || urepo.Repo.AccountDeleteCodeExpiresAt == nil { 53 + if urepo.AccountDeleteCode == nil || urepo.AccountDeleteCodeExpiresAt == nil { 54 54 logger.Error("no deletion token found for account") 55 55 s.writeJSON(w, 400, map[string]any{ 56 56 "error": "InvalidToken", ··· 59 59 return 60 60 } 61 61 62 - if *urepo.Repo.AccountDeleteCode != req.Token { 62 + if *urepo.AccountDeleteCode != req.Token { 63 63 logger.Error("deletion token mismatch") 64 64 s.writeJSON(w, 400, map[string]any{ 65 65 "error": "InvalidToken", ··· 68 68 return 69 69 } 70 70 71 - if time.Now().UTC().After(*urepo.Repo.AccountDeleteCodeExpiresAt) { 71 + if time.Now().UTC().After(*urepo.AccountDeleteCodeExpiresAt) { 72 72 logger.Error("deletion token expired") 73 73 s.writeJSON(w, 400, map[string]any{ 74 74 "error": "ExpiredToken", ··· 155 155 return 156 156 } 157 157 158 - s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 158 + if err := s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 159 159 RepoAccount: &atproto.SyncSubscribeRepos_Account{ 160 160 Active: false, 161 161 Did: req.Did, ··· 163 163 Seq: time.Now().UnixMicro(), 164 164 Time: time.Now().Format(util.ISO8601), 165 165 }, 166 - }) 166 + }); err != nil { 167 + s.logger.Error("failed to add event", "error", err) 168 + } 167 169 168 170 w.WriteHeader(http.StatusOK) 169 171 }
+1 -1
server/handle_server_request_account_delete.go
··· 25 25 } 26 26 27 27 if urepo.Email != "" { 28 - if err := s.sendAccountDeleteEmail(urepo.Email, urepo.Actor.Handle, token); err != nil { 28 + if err := s.sendAccountDeleteEmail(urepo.Email, urepo.Handle, token); err != nil { 29 29 logger.Error("error sending account deletion email", "error", err) 30 30 } 31 31 }
+2 -1
server/handle_server_resolve_handle.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "pkg.rbrt.fr/vow/identity" 5 6 "net/http" 6 7 7 8 "github.com/Azure/go-autorest/autorest/to" ··· 29 30 return 30 31 } 31 32 32 - ctx := context.WithValue(r.Context(), "skip-cache", true) 33 + ctx := context.WithValue(r.Context(), identity.SkipCacheKey, true) 33 34 did, err := s.passport.ResolveHandle(ctx, parsed.String()) 34 35 if err != nil { 35 36 logger.Error("error resolving handle", "error", err)
+4 -2
server/handle_sync_get_blob.go
··· 105 105 w.Header().Set("Content-Disposition", "attachment; filename="+c.String()) 106 106 w.Header().Set("Content-Type", "application/octet-stream") 107 107 w.WriteHeader(http.StatusOK) 108 - io.Copy(w, buf) 108 + if _, err := io.Copy(w, buf); err != nil { 109 + logger.Error("failed to write blob response", "error", err) 110 + } 109 111 } 110 112 111 113 // fetchBlobFromIPFS retrieves blob data for the given CID from the local Kubo ··· 127 129 if err != nil { 128 130 return nil, fmt.Errorf("error calling ipfs cat: %w", err) 129 131 } 130 - defer resp.Body.Close() 132 + defer func() { _ = resp.Body.Close() }() 131 133 132 134 if resp.StatusCode != http.StatusOK { 133 135 msg, _ := io.ReadAll(resp.Body)
+3 -1
server/handle_sync_get_blocks.go
··· 90 90 91 91 w.Header().Set("Content-Type", "application/vnd.ipld.car") 92 92 w.WriteHeader(http.StatusOK) 93 - w.Write(buf.Bytes()) 93 + if _, err := w.Write(buf.Bytes()); err != nil { 94 + logger.Error("failed to write response", "error", err) 95 + } 94 96 }
+3 -1
server/handle_sync_get_record.go
··· 62 62 63 63 w.Header().Set("Content-Type", "application/vnd.ipld.car") 64 64 w.WriteHeader(http.StatusOK) 65 - w.Write(buf.Bytes()) 65 + if _, err := w.Write(buf.Bytes()); err != nil { 66 + logger.Error("failed to write response", "error", err) 67 + } 66 68 }
+3 -1
server/handle_sync_get_repo.go
··· 71 71 72 72 w.Header().Set("Content-Type", "application/vnd.ipld.car") 73 73 w.WriteHeader(http.StatusOK) 74 - w.Write(buf.Bytes()) 74 + if _, err := w.Write(buf.Bytes()); err != nil { 75 + logger.Error("failed to write response", "error", err) 76 + } 75 77 }
+3 -3
server/handle_well_known.go
··· 6 6 "strings" 7 7 8 8 "github.com/Azure/go-autorest/autorest/to" 9 - "pkg.rbrt.fr/vow/internal/helpers" 10 9 "gorm.io/gorm" 10 + "pkg.rbrt.fr/vow/internal/helpers" 11 11 ) 12 12 13 13 var ( ··· 81 81 82 82 if host == s.config.Hostname { 83 83 w.Header().Set("Content-Type", "text/plain") 84 - fmt.Fprint(w, s.config.Did) 84 + _, _ = fmt.Fprint(w, s.config.Did) 85 85 return 86 86 } 87 87 ··· 103 103 } 104 104 105 105 w.Header().Set("Content-Type", "text/plain") 106 - fmt.Fprint(w, actor.Did) 106 + _, _ = fmt.Fprint(w, actor.Did) 107 107 } 108 108 109 109 func (s *Server) handleOauthProtectedResource(w http.ResponseWriter, r *http.Request) {
+1 -1
server/ipfs.go
··· 60 60 if err != nil { 61 61 return fmt.Errorf("error calling pinning service: %w", err) 62 62 } 63 - defer resp.Body.Close() 63 + defer func() { _ = resp.Body.Close() }() 64 64 65 65 // The Pinning Service API returns 202 Accepted on success. 66 66 if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK {
+19 -8
server/repo.go
··· 22 22 "github.com/bluesky-social/indigo/repo" 23 23 blocks "github.com/ipfs/go-block-format" 24 24 "github.com/ipfs/go-cid" 25 - blockstore "github.com/ipfs/go-ipfs-blockstore" 26 25 cbor "github.com/ipfs/go-ipld-cbor" 27 26 "github.com/ipld/go-car" 28 - "github.com/multiformats/go-multihash" 29 27 "gorm.io/gorm/clause" 30 28 "pkg.rbrt.fr/vow/internal/db" 31 29 "pkg.rbrt.fr/vow/metrics" ··· 128 126 return err 129 127 } 130 128 131 - w.Write(data) 132 - 133 - return nil 129 + _, err = w.Write(data) 130 + return err 134 131 } 135 132 136 133 type ApplyWriteResult struct { ··· 243 240 244 241 dbs := rm.s.getBlockstore(urepo.Did) 245 242 bs := recording_blockstore.New(dbs) 243 + <<<<<<< HEAD 244 + ======= 245 + r, err := repo.OpenRepo(ctx, bs, rootcid) 246 + if err != nil { 247 + return nil, fmt.Errorf("error opening repo: %w", err) 248 + } 249 + >>>>>>> 4a24227 (refactor: fix lint + configure tangled ci) 246 250 247 251 var results []ApplyWriteResult 248 252 var ops []*atp.Operation ··· 424 428 Roots: []cid.Cid{newroot}, 425 429 Version: 1, 426 430 }) 431 + if err != nil { 432 + return nil, fmt.Errorf("error dumping car header: %w", err) 433 + } 427 434 if _, err := carstore.LdWrite(buf, hb); err != nil { 428 435 return nil, err 429 436 } ··· 518 525 519 526 // NOTE: using the request ctx seems a bit suss here, so using a background context. i'm not sure if this 520 527 // runs sync or not 521 - rm.s.evtman.AddEvent(context.Background(), &events.XRPCStreamEvent{ 528 + if err := rm.s.evtman.AddEvent(context.Background(), &events.XRPCStreamEvent{ 522 529 RepoCommit: &atproto.SyncSubscribeRepos_Commit{ 523 530 Repo: urepo.Did, 524 531 Blocks: buf.Bytes(), ··· 530 537 Ops: repoOps, 531 538 TooBig: false, 532 539 }, 533 - }) 540 + }); err != nil { 541 + rm.s.logger.Error("failed to add event", "error", err) 542 + } 534 543 535 544 if err := rm.s.UpdateRepo(ctx, urepo.Did, newroot, rev); err != nil { 536 545 return nil, err ··· 718 727 } 719 728 case []any: 720 729 for _, v := range val { 721 - deepiter(v) 730 + if err := deepiter(v); err != nil { 731 + return err 732 + } 722 733 } 723 734 } 724 735
+37 -27
server/server.go
··· 28 28 chimiddleware "github.com/go-chi/chi/v5/middleware" 29 29 "github.com/go-playground/validator" 30 30 "github.com/gorilla/sessions" 31 + "github.com/ipfs/go-cid" 32 + "github.com/prometheus/client_golang/prometheus/promhttp" 31 33 "pkg.rbrt.fr/vow/identity" 32 34 "pkg.rbrt.fr/vow/internal/db" 33 35 "pkg.rbrt.fr/vow/internal/helpers" ··· 37 39 "pkg.rbrt.fr/vow/oauth/dpop" 38 40 "pkg.rbrt.fr/vow/oauth/provider" 39 41 "pkg.rbrt.fr/vow/plc" 40 - "github.com/ipfs/go-cid" 41 - "github.com/prometheus/client_golang/prometheus/promhttp" 42 42 43 43 "gorm.io/gorm" 44 44 ) ··· 76 76 } 77 77 78 78 type Server struct { 79 - http *http.Client 80 - httpd *http.Server 81 - mail *mailyak.MailYak 82 - mailLk *sync.Mutex 83 - router *chi.Mux 84 - db *db.DB 85 - plcClient *plc.Client 86 - logger *slog.Logger 87 - config *config 88 - privateKey *ecdsa.PrivateKey 89 - repoman *RepoMan 90 - oauthProvider *provider.Provider 91 - evtman *events.EventManager 92 - passport *identity.Passport 93 - fallbackProxy string 79 + http *http.Client 80 + httpd *http.Server 81 + mail *mailyak.MailYak 82 + mailLk *sync.Mutex 83 + router *chi.Mux 84 + db *db.DB 85 + plcClient *plc.Client 86 + logger *slog.Logger 87 + config *config 88 + privateKey *ecdsa.PrivateKey 89 + repoman *RepoMan 90 + oauthProvider *provider.Provider 91 + evtman *events.EventManager 92 + passport *identity.Passport 93 + 94 94 sessions *sessions.CookieStore 95 95 validator *validator.Validate 96 96 templateRenderer *TemplateRenderer ··· 287 287 r.Use(corsMiddleware) 288 288 289 289 vdtor := validator.New() 290 - vdtor.RegisterValidation("atproto-handle", func(fl validator.FieldLevel) bool { 290 + if err := vdtor.RegisterValidation("atproto-handle", func(fl validator.FieldLevel) bool { 291 291 if _, err := syntax.ParseHandle(fl.Field().String()); err != nil { 292 292 return false 293 293 } 294 294 return true 295 - }) 296 - vdtor.RegisterValidation("atproto-did", func(fl validator.FieldLevel) bool { 295 + }); err != nil { 296 + return nil, fmt.Errorf("failed to register atproto-handle validator: %w", err) 297 + } 298 + if err := vdtor.RegisterValidation("atproto-did", func(fl validator.FieldLevel) bool { 297 299 if _, err := syntax.ParseDID(fl.Field().String()); err != nil { 298 300 return false 299 301 } 300 302 return true 301 - }) 302 - vdtor.RegisterValidation("atproto-rkey", func(fl validator.FieldLevel) bool { 303 + }); err != nil { 304 + return nil, fmt.Errorf("failed to register atproto-did validator: %w", err) 305 + } 306 + if err := vdtor.RegisterValidation("atproto-rkey", func(fl validator.FieldLevel) bool { 303 307 if _, err := syntax.ParseRecordKey(fl.Field().String()); err != nil { 304 308 return false 305 309 } 306 310 return true 307 - }) 308 - vdtor.RegisterValidation("atproto-nsid", func(fl validator.FieldLevel) bool { 311 + }); err != nil { 312 + return nil, fmt.Errorf("failed to register atproto-rkey validator: %w", err) 313 + } 314 + if err := vdtor.RegisterValidation("atproto-nsid", func(fl validator.FieldLevel) bool { 309 315 if _, err := syntax.ParseNSID(fl.Field().String()); err != nil { 310 316 return false 311 317 } 312 318 return true 313 - }) 319 + }); err != nil { 320 + return nil, fmt.Errorf("failed to register atproto-nsid validator: %w", err) 321 + } 314 322 315 323 httpd := &http.Server{ 316 324 Addr: args.Addr, ··· 587 595 588 596 logger.Info("migrating...") 589 597 590 - s.db.AutoMigrate( 598 + if err := s.db.AutoMigrate( 591 599 &models.Actor{}, 592 600 &models.Repo{}, 593 601 &models.InviteCode{}, ··· 600 608 &models.ReservedKey{}, 601 609 &provider.OauthToken{}, 602 610 &provider.OauthAuthorizationRequest{}, 603 - ) 611 + ); err != nil { 612 + return fmt.Errorf("failed to run migrations: %w", err) 613 + } 604 614 605 615 logger.Info("starting vow") 606 616
+2 -3
server/service_auth.go
··· 6 6 "strings" 7 7 8 8 "github.com/bluesky-social/indigo/atproto/atcrypto" 9 - "github.com/bluesky-social/indigo/atproto/identity" 10 9 atproto_identity "github.com/bluesky-social/indigo/atproto/identity" 11 10 "github.com/bluesky-social/indigo/atproto/syntax" 12 11 "github.com/golang-jwt/jwt/v4" ··· 21 20 } 22 21 23 22 func (m *ES256KSigningMethod) Verify(signingString string, signature string, key any) error { 24 - signatureBytes, err := jwt.DecodeSegment(signature) 23 + signatureBytes, err := jwt.DecodeSegment(signature) //nolint:staticcheck 25 24 if err != nil { 26 25 return err 27 26 } ··· 66 65 ServiceEndpoint: service.ServiceEndpoint, 67 66 } 68 67 } 69 - parsedIdentity := atproto_identity.ParseIdentity(&identity.DIDDocument{ 68 + parsedIdentity := atproto_identity.ParseIdentity(&atproto_identity.DIDDocument{ 70 69 DID: did, 71 70 AlsoKnownAs: didDoc.AlsoKnownAs, 72 71 VerificationMethod: verificationMethods,
+3 -1
test.go
··· 20 20 ) 21 21 22 22 func main() { 23 - runFirehoseConsumer("ws://localhost:8080") 23 + if err := runFirehoseConsumer("ws://localhost:8080"); err != nil { 24 + panic(err) 25 + } 24 26 } 25 27 26 28 func runFirehoseConsumer(relayHost string) error {