diff --git a/.github/workflows/backend-linter.yml b/.github/workflows/backend-linter.yml index 66cdf332..8fd4cba7 100644 --- a/.github/workflows/backend-linter.yml +++ b/.github/workflows/backend-linter.yml @@ -32,8 +32,8 @@ jobs: go-version-file: backend/go.mod - name: Run Golangci-lint - uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2 + uses: golangci/golangci-lint-action@dec74fa03096ff515422f71d18d41307cacde373 # v7.0.0 with: - version: v1.64 + version: v2.0.2 working-directory: backend only-new-issues: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 3976f621..a5c66e28 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -2,11 +2,11 @@ name: Unit Tests on: push: branches: [main] - paths: + paths: - "backend/**" pull_request: branches: [main] - paths: + paths: - "backend/**" jobs: @@ -25,6 +25,7 @@ jobs: - name: Run backend unit tests working-directory: backend run: | + set -e -o pipefail go test -v ./... | tee /tmp/TestResults.log - uses: actions/upload-artifact@v4 if: always() diff --git a/Dockerfile b/Dockerfile index 568c741f..6516cbfb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,4 +41,4 @@ EXPOSE 80 ENV APP_ENV=production ENTRYPOINT ["sh", "./scripts/docker/create-user.sh"] -CMD ["sh", "./scripts/docker/entrypoint.sh"] \ No newline at end of file +CMD ["sh", "./scripts/docker/entrypoint.sh"] diff --git a/backend/.golangci.yml b/backend/.golangci.yml index bb9cf2a6..5bdc098d 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -1,25 +1,64 @@ - +version: "2" +run: + tests: true + timeout: 5m linters: - # Disable all linters. - # Default: false - disable-all: true + default: none enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - contextcheck + - copyloopvar + - durationcheck - errcheck - - gosimple + - errchkjson + - errorlint + - exhaustive + - gocheckcompilerdirectives + - gochecksumtype + - gocognit + - gocritic + - gosec + - gosmopolitan - govet - ineffassign + - loggercheck + - makezero + - musttag + - nilerr + - nilnesserr + - noctx + - protogetter + - reassign + - recvcheck + - rowserrcheck + - spancheck + - sqlclosecheck - staticcheck + - testifylint - unused - - gosec - - gocognit - - presets: - - bugs - - sql + - usestdlibvars + - zerologlint exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling paths: + - third_party$ + - builtin$ + - examples$ - internal/service/test_service.go - -run: - timeout: "5m" - tests: true +formatters: + enable: + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/backend/go.mod b/backend/go.mod index dff063d5..21c70569 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,6 +1,6 @@ module github.com/pocket-id/pocket-id/backend -go 1.23.1 +go 1.23.7 require ( github.com/caarlos0/env/v11 v11.3.1 diff --git a/backend/internal/controller/oidc_controller.go b/backend/internal/controller/oidc_controller.go index f17f5245..201ec39e 100644 --- a/backend/internal/controller/oidc_controller.go +++ b/backend/internal/controller/oidc_controller.go @@ -240,12 +240,13 @@ func (oc *OidcController) EndSessionHandler(c *gin.Context) { var input dto.OidcLogoutDto // Bind query parameters to the struct - if c.Request.Method == http.MethodGet { + switch c.Request.Method { + case http.MethodGet: if err := c.ShouldBindQuery(&input); err != nil { _ = c.Error(err) return } - } else if c.Request.Method == http.MethodPost { + case http.MethodPost: // Bind form parameters to the struct if err := c.ShouldBind(&input); err != nil { _ = c.Error(err) diff --git a/backend/internal/dto/dto_mapper.go b/backend/internal/dto/dto_mapper.go index ba00ca6f..8c027d0b 100644 --- a/backend/internal/dto/dto_mapper.go +++ b/backend/internal/dto/dto_mapper.go @@ -63,13 +63,14 @@ func mapStructInternal(sourceVal reflect.Value, destVal reflect.Value) error { } func mapField(sourceField reflect.Value, destField reflect.Value) error { - if sourceField.Type() == destField.Type() { + switch { + case sourceField.Type() == destField.Type(): destField.Set(sourceField) - } else if sourceField.Kind() == reflect.Slice && destField.Kind() == reflect.Slice { + case sourceField.Kind() == reflect.Slice && destField.Kind() == reflect.Slice: return mapSlice(sourceField, destField) - } else if sourceField.Kind() == reflect.Struct && destField.Kind() == reflect.Struct { + case sourceField.Kind() == reflect.Struct && destField.Kind() == reflect.Struct: return mapStructInternal(sourceField, destField) - } else { + default: return mapSpecialTypes(sourceField, destField) } return nil @@ -98,8 +99,7 @@ func mapSlice(sourceField reflect.Value, destField reflect.Value) error { } func mapSpecialTypes(sourceField reflect.Value, destField reflect.Value) error { - switch sourceField.Interface().(type) { - case datatype.DateTime: + if _, ok := sourceField.Interface().(datatype.DateTime); ok { if sourceField.Type() == reflect.TypeOf(datatype.DateTime{}) && destField.Type() == reflect.TypeOf(time.Time{}) { dateValue := sourceField.Interface().(datatype.DateTime) destField.Set(reflect.ValueOf(dateValue.ToTime())) diff --git a/backend/internal/dto/validations.go b/backend/internal/dto/validations.go index e2ff7b82..5cba3c73 100644 --- a/backend/internal/dto/validations.go +++ b/backend/internal/dto/validations.go @@ -1,10 +1,11 @@ package dto import ( - "github.com/gin-gonic/gin/binding" - "github.com/go-playground/validator/v10" "log" "regexp" + + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/validator/v10" ) var validateUsername validator.Func = func(fl validator.FieldLevel) bool { diff --git a/backend/internal/middleware/cors.go b/backend/internal/middleware/cors.go index d1c16f94..955c1a5f 100644 --- a/backend/internal/middleware/cors.go +++ b/backend/internal/middleware/cors.go @@ -1,6 +1,8 @@ package middleware import ( + "net/http" + "github.com/gin-gonic/gin" "github.com/pocket-id/pocket-id/backend/internal/common" ) @@ -23,7 +25,7 @@ func (m *CorsMiddleware) Add() gin.HandlerFunc { c.Writer.Header().Set("Access-Control-Allow-Headers", "*") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") - if c.Request.Method == "OPTIONS" { + if c.Request.Method == http.MethodOptions { c.AbortWithStatus(204) return } diff --git a/backend/internal/model/api_key.go b/backend/internal/model/api_key.go index 456bcb05..2fcf3703 100644 --- a/backend/internal/model/api_key.go +++ b/backend/internal/model/api_key.go @@ -1,8 +1,6 @@ package model -import ( - "github.com/pocket-id/pocket-id/backend/internal/model/types" -) +import datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" type ApiKey struct { Base diff --git a/backend/internal/model/base.go b/backend/internal/model/base.go index 012f0f36..d4a32121 100644 --- a/backend/internal/model/base.go +++ b/backend/internal/model/base.go @@ -4,7 +4,7 @@ import ( "time" "github.com/google/uuid" - "github.com/pocket-id/pocket-id/backend/internal/model/types" + datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" "gorm.io/gorm" ) diff --git a/backend/internal/service/api_key_service.go b/backend/internal/service/api_key_service.go index 2f5c49c5..d9bc0179 100644 --- a/backend/internal/service/api_key_service.go +++ b/backend/internal/service/api_key_service.go @@ -2,10 +2,11 @@ package service import ( "errors" - datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" "log" "time" + datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" + "github.com/pocket-id/pocket-id/backend/internal/common" "github.com/pocket-id/pocket-id/backend/internal/dto" "github.com/pocket-id/pocket-id/backend/internal/model" diff --git a/backend/internal/service/custom_claim_service.go b/backend/internal/service/custom_claim_service.go index 18546afe..b43ebe0a 100644 --- a/backend/internal/service/custom_claim_service.go +++ b/backend/internal/service/custom_claim_service.go @@ -105,9 +105,10 @@ func (s *CustomClaimService) updateCustomClaims(idType idType, value string, cla Value: claim.Value, } - if idType == UserID { + switch idType { + case UserID: customClaim.UserID = &value - } else if idType == UserGroupID { + case UserGroupID: customClaim.UserGroupID = &value } diff --git a/backend/internal/service/jwt_service_test.go b/backend/internal/service/jwt_service_test.go index fdf7fffc..33170a85 100644 --- a/backend/internal/service/jwt_service_test.go +++ b/backend/internal/service/jwt_service_test.go @@ -319,7 +319,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) { assert.False(t, isAdmin, "isAdmin should be false") audience, ok := claims.Audience() _ = assert.True(t, ok, "Audience not found in token") && - assert.EqualValues(t, []string{"https://test.example.com"}, audience, "Audience should contain the app URL") + assert.Equal(t, []string{"https://test.example.com"}, audience, "Audience should contain the app URL") // Check token expiration time is approximately 1 hour from now expectedExp := time.Now().Add(1 * time.Hour) @@ -606,7 +606,7 @@ func TestGenerateVerifyIdToken(t *testing.T) { assert.Equal(t, "user123", subject, "Token subject should match user ID") audience, ok := claims.Audience() _ = assert.True(t, ok, "Audience not found in token") && - assert.EqualValues(t, []string{clientID}, audience, "Audience should contain the client ID") + assert.Equal(t, []string{clientID}, audience, "Audience should contain the client ID") issuer, ok := claims.Issuer() _ = assert.True(t, ok, "Issuer not found in token") && assert.Equal(t, common.EnvConfig.AppURL, issuer, "Issuer should match app URL") @@ -935,7 +935,7 @@ func TestGenerateVerifyOauthAccessToken(t *testing.T) { assert.Equal(t, user.ID, subject, "Token subject should match user ID") audience, ok := claims.Audience() _ = assert.True(t, ok, "Audience not found in token") && - assert.EqualValues(t, []string{clientID}, audience, "Audience should contain the client ID") + assert.Equal(t, []string{clientID}, audience, "Audience should contain the client ID") issuer, ok := claims.Issuer() _ = assert.True(t, ok, "Issuer not found in token") && assert.Equal(t, common.EnvConfig.AppURL, issuer, "Issuer should match app URL") @@ -1050,7 +1050,7 @@ func TestGenerateVerifyOauthAccessToken(t *testing.T) { assert.Equal(t, user.ID, subject, "Token subject should match user ID") audience, ok := claims.Audience() _ = assert.True(t, ok, "Audience not found in token") && - assert.EqualValues(t, []string{clientID}, audience, "Audience should contain the client ID") + assert.Equal(t, []string{clientID}, audience, "Audience should contain the client ID") // Verify the key type is OKP publicKey, err := service.GetPublicJWK() @@ -1104,7 +1104,7 @@ func TestGenerateVerifyOauthAccessToken(t *testing.T) { assert.Equal(t, user.ID, subject, "Token subject should match user ID") audience, ok := claims.Audience() _ = assert.True(t, ok, "Audience not found in token") && - assert.EqualValues(t, []string{clientID}, audience, "Audience should contain the client ID") + assert.Equal(t, []string{clientID}, audience, "Audience should contain the client ID") // Verify the key type is EC publicKey, err := service.GetPublicJWK() @@ -1158,7 +1158,7 @@ func TestGenerateVerifyOauthAccessToken(t *testing.T) { assert.Equal(t, user.ID, subject, "Token subject should match user ID") audience, ok := claims.Audience() _ = assert.True(t, ok, "Audience not found in token") && - assert.EqualValues(t, []string{clientID}, audience, "Audience should contain the client ID") + assert.Equal(t, []string{clientID}, audience, "Audience should contain the client ID") // Verify the key type is RSA publicKey, err := service.GetPublicJWK() diff --git a/backend/internal/utils/aaguid_util_test.go b/backend/internal/utils/aaguid_util_test.go index e10da1ba..8433ccf1 100644 --- a/backend/internal/utils/aaguid_util_test.go +++ b/backend/internal/utils/aaguid_util_test.go @@ -53,8 +53,8 @@ func TestGetAuthenticatorName(t *testing.T) { // Inject a test AAGUID map aaguidMap = map[string]string{ - "adce0002-35bc-c60a-648b-m0b25f1f05503": "Test Authenticator", - "00000000-0000-0000-0000-000000000000": "Zero Authenticator", + "adce0002-35bc-c60a-648b-0b25f1f05503": "Test Authenticator", + "00000000-0000-0000-0000-000000000000": "Zero Authenticator", } aaguidMapOnce = sync.Once{} aaguidMapOnce.Do(func() {}) // Mark as done to avoid loading from file diff --git a/backend/internal/utils/email/composer.go b/backend/internal/utils/email/composer.go index b85abbd4..eb03bb9f 100644 --- a/backend/internal/utils/email/composer.go +++ b/backend/internal/utils/email/composer.go @@ -171,14 +171,12 @@ func (c *Composer) String() string { func convertRunes(str string) []string { var enc = make([]string, 0, len(str)) for _, r := range str { - if r == ' ' { + switch { + case r == ' ': enc = append(enc, "_") - } else if isPrintableASCIIRune(r) && - r != '=' && - r != '?' && - r != '_' { + case isPrintableASCIIRune(r) && r != '=' && r != '?' && r != '_': enc = append(enc, string(r)) - } else { + default: enc = append(enc, string(toHex([]byte(string(r))))) } }