From 3da9a5b18b95609589ac095fa5cd30710df83b10 Mon Sep 17 00:00:00 2001 From: Stavros Date: Sat, 9 May 2026 18:00:41 +0300 Subject: [PATCH] feat: add golangci-lint for go linting --- .golangci.yml | 17 +++++++++++++++++ cmd/tinyauth/generate_totp.go | 5 +---- cmd/tinyauth/healthcheck.go | 6 +++--- internal/bootstrap/app_bootstrap.go | 2 +- internal/bootstrap/service_bootstrap.go | 2 +- internal/controller/oauth_controller.go | 6 ++++++ internal/controller/user_controller_test.go | 12 ++++++------ internal/middleware/context_middleware.go | 2 +- internal/service/auth_service.go | 7 ------- internal/service/ldap_service.go | 2 +- internal/service/oauth_extractors.go | 2 +- internal/utils/fs_utils_test.go | 2 +- internal/utils/security_utils.go | 2 +- internal/utils/security_utils_test.go | 2 +- internal/utils/string_utils_test.go | 2 +- internal/utils/user_utils_test.go | 2 +- 16 files changed, 43 insertions(+), 30 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..26126fd3 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,17 @@ +version: "2" +linters: + settings: + errcheck: + exclude-functions: + - (http.ResponseWriter).Write + - (http.ResponseWriter).WriteString + - (github.com/gin-gonic/gin.ResponseWriter).Write + - (github.com/gin-gonic/gin.ResponseWriter).WriteString + exclusions: + rules: + - linters: + - errcheck + text: "//nolint:errcheck" + - linters: + - staticcheck + text: "//nolint:staticcheck" diff --git a/cmd/tinyauth/generate_totp.go b/cmd/tinyauth/generate_totp.go index 8819922e..53a7745e 100644 --- a/cmd/tinyauth/generate_totp.go +++ b/cmd/tinyauth/generate_totp.go @@ -68,10 +68,7 @@ func generateTotpCmd() *cli.Command { return fmt.Errorf("failed to parse user: %w", err) } - docker := false - if strings.Contains(tCfg.User, "$$") { - docker = true - } + docker := strings.Contains(tCfg.User, "$$") if user.TOTPSecret != "" { return fmt.Errorf("user already has a TOTP secret") diff --git a/cmd/tinyauth/healthcheck.go b/cmd/tinyauth/healthcheck.go index 649a68c7..93cc8f8c 100644 --- a/cmd/tinyauth/healthcheck.go +++ b/cmd/tinyauth/healthcheck.go @@ -9,8 +9,8 @@ import ( "os" "time" - "github.com/tinyauthapp/tinyauth/internal/utils/tlog" "github.com/tinyauthapp/paerser/cli" + "github.com/tinyauthapp/tinyauth/internal/utils/tlog" ) type healthzResponse struct { @@ -45,7 +45,7 @@ func healthcheckCmd() *cli.Command { } if appUrl == "" { - return errors.New("Could not determine app URL") + return errors.New("could not determine app url") } tlog.App.Info().Str("app_url", appUrl).Msg("Performing health check") @@ -70,7 +70,7 @@ func healthcheckCmd() *cli.Command { return fmt.Errorf("service is not healthy, got: %s", resp.Status) } - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck var healthResp healthzResponse diff --git a/internal/bootstrap/app_bootstrap.go b/internal/bootstrap/app_bootstrap.go index 5b342c48..ca51aa50 100644 --- a/internal/bootstrap/app_bootstrap.go +++ b/internal/bootstrap/app_bootstrap.go @@ -292,7 +292,7 @@ func (app *BootstrapApp) heartbeatRoutine() { continue } - res.Body.Close() + res.Body.Close() //nolint:errcheck if res.StatusCode != 200 && res.StatusCode != 201 { tlog.App.Debug().Str("status", res.Status).Msg("Heartbeat returned non-200/201 status") diff --git a/internal/bootstrap/service_bootstrap.go b/internal/bootstrap/service_bootstrap.go index 09485bd0..bd0e458a 100644 --- a/internal/bootstrap/service_bootstrap.go +++ b/internal/bootstrap/service_bootstrap.go @@ -36,7 +36,7 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er if err != nil { tlog.App.Warn().Err(err).Msg("Failed to setup LDAP service, starting without it") - ldapService.Unconfigure() + ldapService.Unconfigure() //nolint:errcheck } services.ldapService = ldapService diff --git a/internal/controller/oauth_controller.go b/internal/controller/oauth_controller.go index 7f6d6ce0..f3187c4e 100644 --- a/internal/controller/oauth_controller.go +++ b/internal/controller/oauth_controller.go @@ -166,6 +166,12 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) { user, err := controller.auth.GetOAuthUserinfo(sessionIdCookie) + if err != nil { + tlog.App.Error().Err(err).Msg("Failed to get user info from OAuth provider") + c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL)) + return + } + if user.Email == "" { tlog.App.Error().Msg("OAuth provider did not return an email") c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL)) diff --git a/internal/controller/user_controller_test.go b/internal/controller/user_controller_test.go index 4863c16e..f2a77194 100644 --- a/internal/controller/user_controller_test.go +++ b/internal/controller/user_controller_test.go @@ -179,7 +179,7 @@ func TestUserController(t *testing.T) { { description: "Should rate limit on 3 invalid attempts", middlewares: []gin.HandlerFunc{}, - run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { + run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { //nolint:staticcheck loginReq := controller.LoginRequest{ Username: "testuser", Password: "wrongpassword", @@ -201,7 +201,7 @@ func TestUserController(t *testing.T) { } // 4th attempt should be rate limited - recorder = httptest.NewRecorder() + recorder = httptest.NewRecorder() //nolint:staticcheck req := httptest.NewRequest("POST", "/api/user/login", strings.NewReader(string(loginReqBody))) req.Header.Set("Content-Type", "application/json") @@ -293,7 +293,7 @@ func TestUserController(t *testing.T) { middlewares: []gin.HandlerFunc{ totpCtx, }, - run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { + run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { //nolint:staticcheck _, err := queries.CreateSession(context.TODO(), repository.CreateSessionParams{ UUID: "test-totp-login-uuid", Username: "test", @@ -316,7 +316,7 @@ func TestUserController(t *testing.T) { totpReqBody, err := json.Marshal(totpReq) assert.NoError(t, err) - recorder = httptest.NewRecorder() + recorder = httptest.NewRecorder() //nolint:staticcheck req := httptest.NewRequest("POST", "/api/user/totp", strings.NewReader(string(totpReqBody))) req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{ @@ -345,7 +345,7 @@ func TestUserController(t *testing.T) { middlewares: []gin.HandlerFunc{ totpCtx, }, - run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { + run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { //nolint:staticcheck for range 3 { totpReq := controller.TotpRequest{ Code: "000000", // invalid code @@ -354,7 +354,7 @@ func TestUserController(t *testing.T) { totpReqBody, err := json.Marshal(totpReq) assert.NoError(t, err) - recorder = httptest.NewRecorder() + recorder = httptest.NewRecorder() //nolint:staticcheck req := httptest.NewRequest("POST", "/api/user/totp", strings.NewReader(string(totpReqBody))) req.Header.Set("Content-Type", "application/json") diff --git a/internal/middleware/context_middleware.go b/internal/middleware/context_middleware.go index 88e96462..8e024c3a 100644 --- a/internal/middleware/context_middleware.go +++ b/internal/middleware/context_middleware.go @@ -171,7 +171,7 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string) (*model } if !m.auth.IsEmailWhitelisted(userContext.OAuth.Email) { - m.auth.DeleteSession(ctx, uuid) + m.auth.DeleteSession(ctx, uuid) //nolint:errcheck return nil, nil, fmt.Errorf("email from session cookie not whitelisted: %s", userContext.OAuth.Email) } } diff --git a/internal/service/auth_service.go b/internal/service/auth_service.go index 16c53fe0..72a3ab74 100644 --- a/internal/service/auth_service.go +++ b/internal/service/auth_service.go @@ -845,10 +845,3 @@ func (auth *AuthService) ClearRateLimitsTestingOnly() { } auth.loginMutex.Unlock() } - -func (auth *AuthService) getCookieDomain() string { - if auth.config.SubdomainsEnabled { - return "." + auth.config.CookieDomain - } - return auth.config.CookieDomain -} diff --git a/internal/service/ldap_service.go b/internal/service/ldap_service.go index 0963ebf5..d1ab71c4 100644 --- a/internal/service/ldap_service.go +++ b/internal/service/ldap_service.go @@ -269,7 +269,7 @@ func (ldap *LdapService) reconnect() error { exp.Reset() operation := func() (*ldapgo.Conn, error) { - ldap.conn.Close() + ldap.conn.Close() //nolint:errcheck conn, err := ldap.connect() if err != nil { return nil, err diff --git a/internal/service/oauth_extractors.go b/internal/service/oauth_extractors.go index 821a02ca..b93e6f6d 100644 --- a/internal/service/oauth_extractors.go +++ b/internal/service/oauth_extractors.go @@ -92,7 +92,7 @@ func simpleReq[T any](client *http.Client, url string, headers map[string]string if err != nil { return nil, err } - defer res.Body.Close() + defer res.Body.Close() //nolint:errcheck if res.StatusCode < 200 || res.StatusCode >= 300 { return nil, fmt.Errorf("request failed with status: %s", res.Status) diff --git a/internal/utils/fs_utils_test.go b/internal/utils/fs_utils_test.go index 68154419..a38da86f 100644 --- a/internal/utils/fs_utils_test.go +++ b/internal/utils/fs_utils_test.go @@ -18,7 +18,7 @@ func TestReadFile(t *testing.T) { err = file.Close() require.NoError(t, err) - defer os.Remove("/tmp/tinyauth_test_file") + defer os.Remove("/tmp/tinyauth_test_file") //nolint:errcheck // Normal case content, err := ReadFile("/tmp/tinyauth_test_file") diff --git a/internal/utils/security_utils.go b/internal/utils/security_utils.go index abfdbfe8..62349cdc 100644 --- a/internal/utils/security_utils.go +++ b/internal/utils/security_utils.go @@ -53,7 +53,7 @@ func FilterIP(filter string, ip string) (bool, error) { return false, errors.New("invalid IP address") } - filter = strings.Replace(filter, "-", "/", -1) + filter = strings.ReplaceAll(filter, "-", "/") if strings.Contains(filter, "/") { _, cidr, err := net.ParseCIDR(filter) diff --git a/internal/utils/security_utils_test.go b/internal/utils/security_utils_test.go index 6feac4ca..1e5483f4 100644 --- a/internal/utils/security_utils_test.go +++ b/internal/utils/security_utils_test.go @@ -19,7 +19,7 @@ func TestGetSecret(t *testing.T) { err = file.Close() require.NoError(t, err) - defer os.Remove("/tmp/tinyauth_test_secret") + defer os.Remove("/tmp/tinyauth_test_secret") //nolint:errcheck // Get from config assert.Equal(t, "mysecret", utils.GetSecret("mysecret", "")) diff --git a/internal/utils/string_utils_test.go b/internal/utils/string_utils_test.go index 9748e050..896d0bfb 100644 --- a/internal/utils/string_utils_test.go +++ b/internal/utils/string_utils_test.go @@ -73,7 +73,7 @@ func TestGetStringList(t *testing.T) { err = file.Close() assert.NoError(t, err) - defer os.Remove("/tmp/tinyauth_list_test_file") + defer os.Remove("/tmp/tinyauth_list_test_file") //nolint:errcheck values, err := utils.GetStringList([]string{" first@example.com ", "", "second@example.com"}, "/tmp/tinyauth_list_test_file") assert.NoError(t, err) diff --git a/internal/utils/user_utils_test.go b/internal/utils/user_utils_test.go index 973be918..17efe74f 100644 --- a/internal/utils/user_utils_test.go +++ b/internal/utils/user_utils_test.go @@ -24,7 +24,7 @@ func TestGetUsers(t *testing.T) { err = file.Close() require.NoError(t, err) - defer os.Remove(tmpDir + "/tinyauth_users_test.txt") + defer os.Remove(tmpDir + "/tinyauth_users_test.txt") //nolint:errcheck noAttrs := map[string]model.UserAttributes{}