Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e2356a7
chore: migrate from aws-sdk-go to aws-sdk-go-v2 (#370)
mschfh Oct 24, 2025
4af0332
fix(deps): update module github.com/go-chi/chi/v5 to v5.2.3 (#366)
renovate[bot] Oct 24, 2025
935d589
chore(lint): fixes (#378)
mschfh Nov 5, 2025
e10d638
chore(lint): fix perfsprint findings
mschfh Nov 5, 2025
b052dce
chore(lint): fix embeddedstructfieldcheck findings
mschfh Nov 5, 2025
391cbe3
chore(lint): fix whitespace findings
mschfh Nov 5, 2025
300914f
refactor(auth): simplify blocked client ID check using slices.Contains
mschfh Nov 5, 2025
290eb6e
refactor(datastore): use min() for batch size calculation
mschfh Nov 5, 2025
5b4b0ae
chore(server): add lint directive to ignore error check during shutdown
mschfh Nov 5, 2025
c4f029c
chore(lint): fix gochecknoinits findings
mschfh Nov 5, 2025
d299f45
chore(lint): fix staticcheck findings
mschfh Nov 11, 2025
ba2c2be
chore(lint): ignore unparam finding for assert func
mschfh Nov 12, 2025
3fac584
chore(lint): fix revive finding
mschfh Nov 12, 2025
3042b8f
Merge pull request #379 from brave/mschfh-lint-fixes
mschfh Nov 19, 2025
b5fe901
chore(deps): bump golang.org/x/crypto from 0.41.0 to 0.45.0
dependabot[bot] Nov 20, 2025
a7f727d
Merge pull request #380 from brave/dependabot/go_modules/golang.org/x…
DJAndries Nov 20, 2025
8b16741
fix(deps): update module github.com/getsentry/sentry-go to v0.39.0
renovate[bot] Dec 1, 2025
5ea7b9d
Merge pull request #368 from brave/renovate/github-com-getsentry-sent…
mihaiplesa Dec 1, 2025
28880b5
chore(deps): update actions/checkout action to v6
renovate[bot] Dec 2, 2025
8c3f81d
Merge pull request #385 from brave/renovate/actions-checkout-6-x
mschfh Dec 2, 2025
e868158
chore(deps): update actions/setup-go action to v6.1.0
renovate[bot] Dec 2, 2025
20b6dfc
Merge pull request #384 from brave/renovate/actions-setup-go-6-x
mschfh Dec 2, 2025
4dc3717
chore(deps): update golangci/golangci-lint-action action to v9
renovate[bot] Dec 2, 2025
5f67bf5
Merge pull request #386 from brave/renovate/golangci-golangci-lint-ac…
mschfh Dec 2, 2025
a236716
fix(deps): update module github.com/redis/go-redis/v9 to v9.17.0
renovate[bot] Dec 2, 2025
3c17d0d
Merge pull request #376 from brave/renovate/github-com-redis-go-redis…
mihaiplesa Dec 2, 2025
af5c631
Add allowlist for elevated device limits
DJAndries Dec 4, 2025
72c41ae
Merge pull request #388 from brave/mschfh-lint-fix
mschfh Dec 4, 2025
7247727
refactor(ci): split ci workflow into lint and test jobs
mschfh Dec 4, 2025
7cd567c
Merge pull request #389 from brave/mschfh-ci-lint-test
mihaiplesa Dec 4, 2025
a13b9bc
Add allowlist for elevated device limits
DJAndries Dec 4, 2025
985a069
Merge branch 'high-limit-client-ids' of github.com:brave/go-sync into…
DJAndries Dec 4, 2025
87a3951
Address PR feedback
DJAndries Dec 4, 2025
e263736
Merge pull request #387 from brave/high-limit-client-ids
DJAndries Dec 8, 2025
0a32b55
Merge remote-tracking branch 'origin/prod' into prod-sync
DJAndries Dec 8, 2025
376b086
Merge pull request #390 from brave/prod-sync
DJAndries Dec 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,24 @@ permissions:
contents: read

jobs:
ci:
name: ci
lint:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: 1.25

- name: lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
uses: golangci/golangci-lint-action@e7fa5ac41e1cf5b7d48e45e42232ce7ada589601 # v9.1.0

test:
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

- name: test
run: make docker && make docker-test
18 changes: 9 additions & 9 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"crypto/ed25519"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"net/http"
"os"
"regexp"
"slices"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -46,7 +48,7 @@ func authenticate(tkn string) (string, error) {
re := regexp.MustCompile(tokenRE)
m := re.FindStringSubmatch(string(base64DecodedBytes))
if m == nil {
return "", fmt.Errorf("invalid token format")
return "", errors.New("invalid token format")
}
token := Token{TimestampHex: m[1], SignedTimestampHex: m[2], PublicKeyHex: m[3]}

Expand All @@ -64,7 +66,7 @@ func authenticate(tkn string) (string, error) {
return "", fmt.Errorf("error decoding hex string: %w", err)
}
if !ed25519.Verify(publicKey, timestampBytes, signedTimestamp) {
return "", fmt.Errorf("signature verification failed")
return "", errors.New("signature verification failed")
}

var timestamp int64
Expand All @@ -75,14 +77,12 @@ func authenticate(tkn string) (string, error) {

// Verify that this token is within +/- 1 day.
if abs(time.Now().UnixMilli()-timestamp) > TokenMaxDuration {
return "", fmt.Errorf("token is expired")
return "", errors.New("token is expired")
}

blockedIDs := strings.Split(os.Getenv("BLOCKED_CLIENT_IDS"), ",")
for _, id := range blockedIDs {
if token.PublicKeyHex == id {
return "", fmt.Errorf("This client ID is blocked")
}
if slices.Contains(blockedIDs, token.PublicKeyHex) {
return "", errors.New("this client ID is blocked")
}

return token.PublicKeyHex, nil
Expand All @@ -98,12 +98,12 @@ func Authorize(r *http.Request) (string, error) {
if ok && len(tokens) >= 1 {
token = tokens[0]
if !strings.HasPrefix(token, bearerPrefix) {
return "", fmt.Errorf("Not a valid token")
return "", errors.New("not a valid token")
}
token = strings.TrimPrefix(token, bearerPrefix)
}
if token == "" {
return "", fmt.Errorf("Not a valid token")
return "", errors.New("not a valid token")
}

// Verify token
Expand Down
16 changes: 9 additions & 7 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package auth_test

import (
"encoding/base64"
"errors"
"fmt"
"net/http"
"testing"
"time"

"github.com/stretchr/testify/suite"

"github.com/brave/go-sync/auth"
"github.com/brave/go-sync/auth/authtest"
"github.com/stretchr/testify/suite"
)

type AuthTestSuite struct {
Expand All @@ -20,14 +22,14 @@ func (suite *AuthTestSuite) TestAuthenticate() {
// invalid token format
id, err := auth.Authenticate(base64.URLEncoding.EncodeToString([]byte("||")))
suite.Require().Error(err, "invalid token format should fail")
suite.Require().Equal("", id, "empty clientID should be returned")
suite.Require().Empty(id, "empty clientID should be returned")

// invalid signature
_, tokenHex, _, err := authtest.GenerateToken(time.Now().UnixMilli())
suite.Require().NoError(err, "generate token should succeed")
id, err = auth.Authenticate(base64.URLEncoding.EncodeToString([]byte("12" + tokenHex)))
suite.Require().Error(err, "invalid signature should fail")
suite.Require().Equal("", id)
suite.Require().Empty(id)

// valid token
tkn, _, expectedID, err := authtest.GenerateToken(time.Now().UnixMilli())
Expand All @@ -41,13 +43,13 @@ func (suite *AuthTestSuite) TestAuthenticate() {
suite.Require().NoError(err, "generate token should succeed")
id, err = auth.Authenticate(tkn)
suite.Require().Error(err, "outdated token should failed")
suite.Require().Equal("", id)
suite.Require().Empty(id)

tkn, _, _, err = authtest.GenerateToken(time.Now().UnixMilli() + auth.TokenMaxDuration + 100)
suite.Require().NoError(err, "generate token should succeed")
id, err = auth.Authenticate(tkn)
suite.Require().Error(err, "outdated token should failed")
suite.Require().Equal("", id)
suite.Require().Empty(id)
}

func (suite *AuthTestSuite) TestAuthorize() {
Expand All @@ -59,8 +61,8 @@ func (suite *AuthTestSuite) TestAuthorize() {
outdatedToken, _, _, err := authtest.GenerateToken(time.Now().UnixMilli() - auth.TokenMaxDuration - 1)
suite.Require().NoError(err, "generate token should succeed")

invalidTokenErr := fmt.Errorf("Not a valid token")
outdatedErr := fmt.Errorf("error authorizing: %w", fmt.Errorf("token is expired"))
invalidTokenErr := errors.New("not a valid token")
outdatedErr := fmt.Errorf("error authorizing: %w", errors.New("token is expired"))
tests := map[string]struct {
token string
clientID string
Expand Down
2 changes: 1 addition & 1 deletion cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func GetInterimCountKey(clientID string, countType string) string {
return clientID + "#interim_" + countType
}

// GetAndClearInterimCount returns the amount of entities inserted in
// GetInterimCount returns the amount of entities inserted in
// the DB that were not yet added to the item count
func (c *Cache) GetInterimCount(ctx context.Context, clientID string, countType string, clearCache bool) (int, error) {
countStr, err := c.Get(ctx, GetInterimCountKey(clientID, countType), clearCache)
Expand Down
6 changes: 4 additions & 2 deletions cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"context"
"testing"

"github.com/brave/go-sync/cache"
"github.com/stretchr/testify/suite"

"github.com/brave/go-sync/cache"
)

type CacheTestSuite struct {
suite.Suite

cache *cache.Cache
}

Expand All @@ -21,7 +23,7 @@ func (suite *CacheTestSuite) TestSetTypeMtime() {
suite.cache.SetTypeMtime(context.Background(), "id", 123, 12345678)
val, err := suite.cache.Get(context.Background(), "id#123", false)
suite.Require().NoError(err)
suite.Require().Equal(val, "12345678")
suite.Require().Equal("12345678", val)
}

func (suite *CacheTestSuite) TestIsTypeMtimeUpdated() {
Expand Down
30 changes: 16 additions & 14 deletions command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package command
import (
"context"
"encoding/binary"
"errors"
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/rs/zerolog/log"

"github.com/brave/go-sync/cache"
"github.com/brave/go-sync/datastore"
"github.com/brave/go-sync/schema/protobuf/sync_pb"
"github.com/rs/zerolog/log"
)

var (
Expand All @@ -25,7 +27,6 @@ const (
setSyncPollInterval int32 = 30
nigoriTypeID int32 = 47745
deviceInfoTypeID int = 154522
maxActiveDevices int = 50
historyCountTypeStr string = "history"
normalCountTypeStr string = "normal"
)
Expand Down Expand Up @@ -54,10 +55,10 @@ func handleGetUpdatesRequest(cache *cache.Cache, guMsg *sync_pb.GetUpdatesMessag
activeDevices++
}

// Error out when exceeds the limit.
if activeDevices >= maxActiveDevices {
// Error out when device limit has been reached.
if hasReachedDeviceLimit(activeDevices, clientID) {
errCode = sync_pb.SyncEnums_THROTTLED
return &errCode, fmt.Errorf("exceed limit of active devices in a chain")
return &errCode, errors.New("exceed limit of active devices in a chain")
}
}

Expand Down Expand Up @@ -125,7 +126,7 @@ func handleGetUpdatesRequest(cache *cache.Cache, guMsg *sync_pb.GetUpdatesMessag

token, n := binary.Varint(guRsp.NewProgressMarker[i].Token)
if n <= 0 {
return nil, fmt.Errorf("Failed at decoding token value %v", token)
return nil, fmt.Errorf("failed at decoding token value %v", token)
}

// Check cache to short circuit with 0 updates for polling requests.
Expand All @@ -150,7 +151,7 @@ func handleGetUpdatesRequest(cache *cache.Cache, guMsg *sync_pb.GetUpdatesMessag
if isNewClient && *fromProgressMarker.DataTypeId == nigoriTypeID &&
token == 0 && len(entities) == 0 {
errCode = sync_pb.SyncEnums_TRANSIENT_ERROR
return &errCode, fmt.Errorf("nigori root folder entity is not ready yet")
return &errCode, errors.New("nigori root folder entity is not ready yet")
}

if hasChangesRemaining {
Expand Down Expand Up @@ -224,7 +225,7 @@ func getInterimItemCounts(cache *cache.Cache, clientID string, clearCache bool)
// - existed sync entity will be updated if version is greater than 0.
func handleCommitRequest(cache *cache.Cache, commitMsg *sync_pb.CommitMessage, commitRsp *sync_pb.CommitResponse, db datastore.Datastore, clientID string) (*sync_pb.SyncEnums_ErrorType, error) {
if commitMsg == nil {
return nil, fmt.Errorf("nil commitMsg is received")
return nil, errors.New("nil commitMsg is received")
}

errCode := sync_pb.SyncEnums_SUCCESS // default value, might be changed later
Expand Down Expand Up @@ -258,6 +259,7 @@ func handleCommitRequest(cache *cache.Cache, commitMsg *sync_pb.CommitMessage, c
// Map to save commit data type ID & mtime
typeMtimeMap := make(map[int]int64)
for i, v := range commitMsg.Entries {
var conflict, deleted bool
entryRsp := &sync_pb.CommitResponse_EntryResponse{}
commitRsp.Entryresponse[i] = entryRsp

Expand Down Expand Up @@ -305,7 +307,7 @@ func handleCommitRequest(cache *cache.Cache, commitMsg *sync_pb.CommitMessage, c
// Insert all non-history items. For history items, ignore any items above history quoto
// and lie to the client about the objects being synced instead of returning OVER_QUOTA
// so the client can continue to sync other entities.
conflict, err := db.InsertSyncEntity(entityToCommit)
conflict, err = db.InsertSyncEntity(entityToCommit)
if err != nil {
log.Error().Err(err).Msg("Insert sync entity failed")
rspType := sync_pb.CommitResponse_TRANSIENT_ERROR
Expand All @@ -330,7 +332,7 @@ func handleCommitRequest(cache *cache.Cache, commitMsg *sync_pb.CommitMessage, c
}
}
} else { // Update
conflict, deleted, err := db.UpdateSyncEntity(entityToCommit, oldVersion)
conflict, deleted, err = db.UpdateSyncEntity(entityToCommit, oldVersion)
if err != nil {
log.Error().Err(err).Msg("Update sync entity failed")
rspType := sync_pb.CommitResponse_TRANSIENT_ERROR
Expand All @@ -354,7 +356,7 @@ func handleCommitRequest(cache *cache.Cache, commitMsg *sync_pb.CommitMessage, c
if err != nil {
log.Error().Err(err).Msg("Interim count update failed")
errCode = sync_pb.SyncEnums_TRANSIENT_ERROR
return &errCode, fmt.Errorf("Interim count update failed: %w", err)
return &errCode, fmt.Errorf("interim count update failed: %w", err)
}

typeMtimeMap[*entityToCommit.DataType] = *entityToCommit.Mtime
Expand Down Expand Up @@ -443,7 +445,7 @@ func HandleClientToServerMessage(cache *cache.Cache, pb *sync_pb.ClientToServerM

var err error
if pb.MessageContents == nil {
return fmt.Errorf("nil pb.MessageContents received")
return errors.New("nil pb.MessageContents received")
} else if *pb.MessageContents == sync_pb.ClientToServerMessage_GET_UPDATES {
guRsp := &sync_pb.GetUpdatesResponse{}
pbRsp.GetUpdates = guRsp
Expand Down Expand Up @@ -487,7 +489,7 @@ func HandleClientToServerMessage(cache *cache.Cache, pb *sync_pb.ClientToServerM
return fmt.Errorf("error handling ClearServerData request: %w", err)
}
} else {
return fmt.Errorf("unsupported message type of ClientToServerMessage")
return errors.New("unsupported message type of ClientToServerMessage")
}

return nil
Expand Down
Loading