From 69d9247695513b8dfbbd3a4bd83b3294176d1073 Mon Sep 17 00:00:00 2001 From: Jonathan0823 Date: Fri, 21 Aug 2026 10:13:21 +0700 Subject: [PATCH 1/2] refactor(core): reduce refresh token service complexity --- internal/core/service/auth.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/internal/core/service/auth.go b/internal/core/service/auth.go index 034d427..19ef3f4 100644 --- a/internal/core/service/auth.go +++ b/internal/core/service/auth.go @@ -223,9 +223,20 @@ func (s *authService) RefreshTokens(ctx context.Context, refreshToken, ip, userA return "", "", fmt.Errorf("hash refresh token: %w", err) } + accessToken, newRawToken, reused, err := s.refreshTokensInTransaction(ctx, hash, ip, userAgent) + if err != nil { + return "", "", err + } + if reused { + return "", "", fmt.Errorf("%w: %w", ErrRefreshTokenReused, domain.ErrUnauthenticated) + } + return accessToken, newRawToken, nil +} + +func (s *authService) refreshTokensInTransaction(ctx context.Context, hash []byte, ip, userAgent string) (string, string, bool, error) { var accessToken, newRawToken string var reused bool - err = s.repo.WithTx(ctx, func(u port.UnitOfWork) error { + err := s.repo.WithTx(ctx, func(u port.UnitOfWork) error { token, err := s.lookupRefreshToken(ctx, u.Auth(), hash) if err != nil { return err @@ -252,13 +263,7 @@ func (s *authService) RefreshTokens(ctx context.Context, refreshToken, ip, userA newRawToken, err = s.rotateRefreshToken(ctx, u.Auth(), token, ip, userAgent) return err }) - if err != nil { - return "", "", err - } - if reused { - return "", "", fmt.Errorf("%w: %w", ErrRefreshTokenReused, domain.ErrUnauthenticated) - } - return accessToken, newRawToken, nil + return accessToken, newRawToken, reused, err } func (s *authService) lookupRefreshToken(ctx context.Context, auth port.AuthRepository, hash []byte) (*domain.RefreshToken, error) { From f7f853981519b52b6fa7c0997c482288cb18b960 Mon Sep 17 00:00:00 2001 From: Jonathan0823 Date: Fri, 21 Aug 2026 10:13:24 +0700 Subject: [PATCH 2/2] refactor(platform): split configuration validation --- internal/platform/config.go | 53 ++++++++++++++++++++++++-------- internal/platform/config_test.go | 25 +++++++++++++++ 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/internal/platform/config.go b/internal/platform/config.go index ee2233a..c01ef9a 100644 --- a/internal/platform/config.go +++ b/internal/platform/config.go @@ -64,12 +64,32 @@ func (c Config) Validate() error { if c.Environment != "development" && c.Environment != "test" && c.Environment != "production" { return fmt.Errorf("ENVIRONMENT must be development, test, or production") } + if err := c.validateAllowedOrigins(); err != nil { + return err + } + if err := c.validateRequiredSettings(); err != nil { + return err + } + if err := c.validateOAuthSettings(); err != nil { + return err + } + if err := c.validateProductionSecrets(); err != nil { + return err + } + return c.RateLimit.Validate(c.Environment) +} + +func (c Config) validateAllowedOrigins() error { for index, origin := range c.AllowedOrigins { parsed, err := url.ParseRequestURI(origin) if origin != "*" && (err != nil || parsed.Scheme != "http" && parsed.Scheme != "https" || parsed.Hostname() == "" || parsed.Path != "" || parsed.RawQuery != "" || parsed.Fragment != "" || parsed.User != nil) { return fmt.Errorf("ALLOWED_ORIGINS[%d] is invalid: %q", index, origin) } } + return nil +} + +func (c Config) validateRequiredSettings() error { required := []struct{ name, value string }{ {"ALLOWED_ORIGINS", first(c.AllowedOrigins)}, {"BASE_URL", c.BaseURL}, @@ -85,26 +105,35 @@ func (c Config) Validate() error { return fmt.Errorf("%s is required", setting.name) } } + return nil +} + +func (c Config) validateOAuthSettings() error { if (c.GitHubClientID == "") != (c.GitHubClientSecret == "") { return fmt.Errorf("GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET must be configured together") } if (c.GoogleClientID == "") != (c.GoogleClientSecret == "") { return fmt.Errorf("GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET must be configured together") } - if c.Environment == "production" { - secrets := []struct{ name, value string }{ - {"JWT_ACCESS_SECRET", c.JWTAccessSecret}, - {"REFRESH_TOKEN_HASH_KEY", c.RefreshTokenHashKey}, - {"SESSION_SECRET", c.SessionSecret}, - {"RATE_LIMIT_KEY", c.RateLimit.Key}, - } - for _, secret := range secrets { - if len(secret.value) < 32 { - return fmt.Errorf("%s must be at least 32 bytes in production", secret.name) - } + return nil +} + +func (c Config) validateProductionSecrets() error { + if c.Environment != "production" { + return nil + } + secrets := []struct{ name, value string }{ + {"JWT_ACCESS_SECRET", c.JWTAccessSecret}, + {"REFRESH_TOKEN_HASH_KEY", c.RefreshTokenHashKey}, + {"SESSION_SECRET", c.SessionSecret}, + {"RATE_LIMIT_KEY", c.RateLimit.Key}, + } + for _, secret := range secrets { + if len(secret.value) < 32 { + return fmt.Errorf("%s must be at least 32 bytes in production", secret.name) } } - return c.RateLimit.Validate(c.Environment) + return nil } func first(values []string) string { diff --git a/internal/platform/config_test.go b/internal/platform/config_test.go index 5291eb6..04cf841 100644 --- a/internal/platform/config_test.go +++ b/internal/platform/config_test.go @@ -32,12 +32,24 @@ func TestConfigValidate(t *testing.T) { t.Fatalf("Validate() error = %v", err) } + cfg = validConfig() + cfg.Environment = "invalid" + if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "ENVIRONMENT") { + t.Fatalf("invalid environment error = %v", err) + } + cfg = validConfig() cfg.AllowedOrigins = []string{"https://example.com", "not-an-origin"} if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "ALLOWED_ORIGINS[1]") { t.Fatalf("invalid second origin error = %v", err) } + cfg = validConfig() + cfg.AllowedOrigins = nil + if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "ALLOWED_ORIGINS") { + t.Fatalf("missing origins error = %v", err) + } + cfg = validConfig() cfg.DatabaseURL = "" if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "DATABASE_URL") { @@ -50,6 +62,19 @@ func TestConfigValidate(t *testing.T) { t.Fatal("Validate succeeded with partial GitHub configuration") } + cfg = validConfig() + cfg.GoogleClientSecret = "secret" + if err := cfg.Validate(); err == nil { + t.Fatal("Validate succeeded with partial Google configuration") + } + + cfg = validConfig() + cfg.Environment = "production" + cfg.RateLimit.AllowMemoryInProduction = true + if err := cfg.Validate(); err != nil { + t.Fatalf("valid production config error = %v", err) + } + cfg = validConfig() cfg.Environment = "production" cfg.JWTAccessSecret = "short"