-
Notifications
You must be signed in to change notification settings - Fork 27
[jwt] add module and config #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
WalkthroughAdds a JWT authentication subsystem and users package, replaces legacy auth repo/cache with a users service and login cache, expands cache primitives/backends, updates handlers and middleware for JWT and scope checks, adds token persistence/migration, metrics, FX wiring, OpenAPI updates and observability artifacts. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant AuthHandler as "3rdparty Auth Handler"
participant JWTSvc as "JWT Service"
participant Repo as "Token Repo"
participant DB as "Database"
Client->>AuthHandler: POST /3rdparty/v1/auth/token (Basic)
AuthHandler->>JWTSvc: GenerateToken(ctx, userID, scopes, ttl)
JWTSvc->>JWTSvc: build claims, sign (HS256)
JWTSvc->>Repo: Insert(ctx, tokenModel)
Repo->>DB: INSERT tokens
Repo-->>JWTSvc: OK
JWTSvc-->>AuthHandler: TokenInfo
AuthHandler-->>Client: 201 Created + token
sequenceDiagram
autonumber
actor Client
participant JWTMw as "JWT Middleware"
participant JWTSvc as "JWT Service"
participant Repo as "Token Repo"
participant UsersSvc as "Users Service"
participant Handler as "Protected Handler"
Client->>JWTMw: Request (Authorization: Bearer <token>)
JWTMw->>JWTSvc: ParseToken(ctx, token)
JWTSvc->>Repo: IsRevoked(ctx, jti)
Repo-->>JWTSvc: false
JWTSvc-->>JWTMw: Claims(userID, scopes)
JWTMw->>UsersSvc: GetByUsername(userID)
UsersSvc-->>JWTMw: *User
JWTMw->>JWTMw: SetUser + SetScopes
JWTMw->>Handler: Next()
Handler-->>Client: Response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (5)
internal/config/config.go (1)
130-132: Consider a shorter default JWT TTL for better security.The default TTL of 1 year is unusually long for JWT tokens. Industry best practice typically ranges from minutes to hours (e.g., 1 hour to 24 hours). Long-lived tokens increase the window of opportunity for token theft and misuse.
If this extended TTL is intentional for the SMS gateway use case, consider adding a comment explaining the rationale.
Consider a more conservative default:
JWT: JWT{ - TTL: Duration(time.Hour * 24 * 365), // 1 year + TTL: Duration(time.Hour * 24), // 24 hours },internal/sms-gateway/jwt/config.go (1)
8-22: Guard against negative TTL values inConfig.ValidateRight now only
TTL == 0is rejected, so a negative duration would pass validation and later produce already-expired tokens, which is almost certainly a misconfiguration. Consider tightening the check to reject non‑positive values:func (c Config) Validate() error { if c.Secret == "" { return fmt.Errorf("%w: secret is required", ErrInvalidConfig) } - if c.TTL == 0 { - return fmt.Errorf("%w: ttl is required", ErrInvalidConfig) - } + if c.TTL <= 0 { + return fmt.Errorf("%w: ttl must be positive", ErrInvalidConfig) + } return nil }internal/sms-gateway/jwt/service.go (3)
20-45: Consider validatingrevokeddependency inNew
Newassumesrevokedis non‑nil; if wiring ever passesnil,IsRevoked/Revokewill panic at runtime. Either validate here and fail fast or document the non‑nil contract explicitly; validation could look like:func New(config Config, revoked *revokedStorage) (Service, error) { if err := config.Validate(); err != nil { return nil, err } + + if revoked == nil { + return nil, fmt.Errorf("%w: revoked storage is required", ErrInvalidConfig) + }
47-72: Tighten TTL handling and consider making issuer configurableTwo small points in
GenerateToken:
- A negative
ttlargument will currently produce an already‑expired token vianow.Add(min(ttl, s.config.TTL)). If that’s not a valid use case, consider rejecting negative values early:func (s *service) GenerateToken(userID string, scopes []string, ttl time.Duration) (string, error) { - if ttl == 0 { + if ttl < 0 { + return "", fmt.Errorf("ttl must be non-negative") + } + + if ttl == 0 { ttl = s.config.TTL }
- The issuer is hardcoded as
"sms-gate.app". If you have multiple environments or brands, you may want this to come from configuration instead of being fixed in code.
74-103: Align algorithm checks and error signaling inParseToken
ParseTokenis generally solid, but you might want to tighten and standardize behavior:
- You currently accept any
SigningMethodHMAC. To strictly mirrorGenerateToken(which always uses HS256), consider restricting to that method:parsedToken, err := jwt.ParseWithClaims(token, new(Claims), func(t *jwt.Token) (any, error) { - if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("%w: %v", ErrUnexpectedSigningMethod, t.Header["alg"]) - } + if t.Method != jwt.SigningMethodHS256 { + return nil, fmt.Errorf("%w: %v", ErrUnexpectedSigningMethod, t.Header["alg"]) + }
- For the
!ok || !parsedToken.Validcase you return a plain"invalid token"error. If callers need to distinguish invalid/expired tokens from other failures (similar to howErrTokenRevokedis used), consider introducing anErrInvalidTokensentinel inerrors.goand returning that here.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (12)
go.mod(1 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/jwt/config.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/revoked.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
internal/sms-gateway/jwt/module.go (6)
internal/config/module.go (1)
Module(23-134)internal/sms-gateway/app.go (1)
Module(34-58)internal/sms-gateway/cache/factory.go (1)
Factory(16-18)internal/config/config.go (2)
Cache(83-85)Config(12-23)internal/sms-gateway/jwt/service.go (2)
New(28-45)Service(14-18)internal/sms-gateway/jwt/config.go (1)
Config(8-11)
internal/config/config.go (1)
internal/config/types.go (1)
Duration(11-11)
internal/sms-gateway/jwt/errors.go (1)
internal/sms-gateway/jwt/service.go (1)
New(28-45)
internal/config/module.go (3)
internal/config/config.go (2)
Config(12-23)JWT(91-94)internal/sms-gateway/jwt/config.go (1)
Config(8-11)internal/config/types.go (1)
Duration(11-11)
internal/sms-gateway/jwt/config.go (2)
internal/config/config.go (1)
Config(12-23)internal/sms-gateway/jwt/errors.go (1)
ErrInvalidConfig(7-7)
internal/sms-gateway/jwt/service.go (3)
internal/sms-gateway/jwt/jwt.go (1)
Claims(5-10)internal/sms-gateway/jwt/config.go (1)
Config(8-11)internal/sms-gateway/jwt/errors.go (2)
ErrUnexpectedSigningMethod(9-9)ErrTokenRevoked(8-8)
internal/sms-gateway/jwt/revoked.go (2)
internal/config/config.go (1)
Cache(83-85)pkg/cache/errors.go (1)
ErrKeyNotFound(7-7)
internal/sms-gateway/app.go (2)
internal/config/module.go (1)
Module(23-134)internal/sms-gateway/jwt/module.go (1)
Module(9-25)
internal/sms-gateway/jwt/disabled.go (3)
internal/sms-gateway/jwt/service.go (1)
Service(14-18)internal/sms-gateway/jwt/errors.go (1)
ErrDisabled(6-6)internal/sms-gateway/jwt/jwt.go (1)
Claims(5-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Test
- GitHub Check: Lint
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (8)
internal/config/types.go (1)
1-48: LGTM! Well-designed Duration type.The implementation is clean and robust:
- Nil-safe accessors prevent potential panics
- Proper error wrapping with context
- Clean delegation from YAML to text unmarshalling
- Interface assertions ensure compile-time compatibility
internal/config/module.go (1)
128-133: LGTM! Config provider follows established patterns.The JWT config provider correctly maps configuration fields and follows the same pattern as other providers in this module.
internal/sms-gateway/app.go (1)
10-10: LGTM! Clean module integration.JWT module is properly integrated into the application's dependency graph following the established pattern.
Also applies to: 57-57
internal/sms-gateway/jwt/jwt.go (1)
1-10: LGTM! Clean Claims structure.The Claims struct properly embeds RegisteredClaims and adds custom fields with appropriate JSON tags, following JWT best practices.
internal/sms-gateway/jwt/module.go (1)
9-25: LGTM! Well-structured FX module.The module properly:
- Provides a named logger for JWT operations
- Creates private cache and revoked storage dependencies
- Conditionally enables JWT based on configuration (empty Secret → disabled service)
- Follows established FX patterns
go.mod (1)
18-18: Dependency verified: v5.3.0 is the latest stable version and includes the security fix for the HIGH severity memory allocation vulnerability (CVE affecting versions < 5.2.2).No action required.
internal/sms-gateway/jwt/errors.go (1)
5-10: Error sentinel set looks appropriateThe exported error values cover the key JWT states (disabled, invalid config, revoked, bad signing method) and are well named for use as sentinels in higher layers.
internal/sms-gateway/jwt/disabled.go (1)
8-27: DisabledServiceimplementation is clear and consistentThe
disabledimplementation cleanly satisfiesServiceand consistently returnsErrDisabledfor all operations, which should make wiring and feature‑flagging straightforward for callers.
4a8df1c to
9a89acb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (5)
internal/config/config.go (2)
22-23: Clarify semantics ofJWT.Secretand potential enable/disable behaviorRight now
JWT.Secrethas no default and an empty string is a valid in-memory value. Depending on howjwt.Configand the JWT module consume this, an empty secret could either:
- silently weaken security (e.g., tokens effectively unsigned or trivially forgeable), or
- cause late failures when signing/verifying tokens.
It’s usually safer to make the configuration explicit:
- Either treat “JWT enabled” as a separate boolean in
JWTand validate thatSecretis non-empty when enabled, or- Fail fast during config loading if
Secretis empty whenever the JWT module is wired in.This keeps the security posture obvious and avoids ambiguous “empty secret means disabled?” semantics.
Also applies to: 91-95
3-4: JWT defaults look reasonable; consider deriving or requiringIssuerfor self‑hosted setupsThe default
TTLof 24 hours and a non-emptyIssuerhelp produce usable tokens out of the box. For self-hosted deployments, though, hard-coding"sms-gate.app"could lead toissclaims that don’t match the actual public host.You might consider either:
- deriving the default
Issuerfrom existing HTTP/API config if present, or- leaving
JWT.Issuerempty by default and requiring it (with validation) when JWT is enabled.Not critical, but it can reduce surprises in multi-tenant or custom-domain environments.
Also applies to: 131-134
internal/config/types.go (2)
11-27: Duration wrapper is solid; minor naming/style nitThe wrapper around
time.Durationwith nil‑safeDuration()/String()is straightforward and fits the config use case.Very minor:
Duration.Duration()reads a bit stuttery at call sites (e.g.,cfg.JWT.TTL.Duration()). If you touch this again before it spreads widely, consider a name likeAsDuration()orValue()for slightly clearer usage. Not a blocker.
29-45: Add nil‑guards in unmarshallers to avoid panics on misuse
UnmarshalTextandUnmarshalYAMLassumedis non‑nil. In normal struct-field usage that’s fine, but a manually used*Duration(or a future optional*Durationconfig field) could end up nil and panic when these methods are called.A small defensive check would make the type more robust with negligible cost. For example:
func (d *Duration) UnmarshalText(text []byte) error { - t, err := time.ParseDuration(string(text)) + if d == nil { + return fmt.Errorf("can't unmarshal duration into nil *Duration") + } + + t, err := time.ParseDuration(string(text)) if err != nil { return fmt.Errorf("can't parse duration: %w", err) } *d = Duration(t) return nil } func (d *Duration) UnmarshalYAML(value *yaml.Node) error { + if d == nil { + return fmt.Errorf("can't unmarshal duration into nil *Duration") + } + var s string if err := value.Decode(&s); err != nil { return fmt.Errorf("can't unmarshal duration: %w", err) } return d.UnmarshalText([]byte(s)) }This keeps behavior the same for normal config paths while hardening against accidental nil usage.
internal/sms-gateway/jwt/config.go (1)
14-24: Consider adding minimum secret length validation.For HMAC-based JWT signing, weak secrets can compromise security. Consider enforcing a minimum secret length (e.g., 32 bytes for HS256).
Example validation:
const minSecretLength = 32 func (c Config) Validate() error { if c.Secret == "" { return fmt.Errorf("%w: secret is required", ErrInvalidConfig) } if len(c.Secret) < minSecretLength { return fmt.Errorf("%w: secret must be at least %d bytes", ErrInvalidConfig, minSecretLength) } if c.TTL <= 0 { return fmt.Errorf("%w: ttl must be positive", ErrInvalidConfig) } return nil }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (12)
go.mod(1 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/jwt/config.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/revoked.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
- go.mod
- internal/sms-gateway/app.go
- internal/sms-gateway/jwt/module.go
- internal/config/module.go
- internal/sms-gateway/jwt/disabled.go
- internal/sms-gateway/jwt/service.go
- internal/sms-gateway/jwt/revoked.go
- internal/sms-gateway/jwt/jwt.go
🧰 Additional context used
🧬 Code graph analysis (3)
internal/config/config.go (1)
internal/config/types.go (1)
Duration(11-11)
internal/sms-gateway/jwt/config.go (2)
internal/config/config.go (1)
Config(12-23)internal/sms-gateway/jwt/errors.go (1)
ErrInvalidConfig(8-8)
internal/sms-gateway/jwt/errors.go (1)
internal/sms-gateway/jwt/service.go (1)
New(28-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (3)
internal/sms-gateway/jwt/errors.go (1)
5-11: LGTM!The error definitions are clear, follow Go conventions, and align well with their usage across the JWT module.
internal/sms-gateway/jwt/config.go (2)
8-12: LGTM!The Config struct is well-defined with appropriate types for JWT configuration.
15-17: Secret validation is correct.The empty string check properly ensures a secret is provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/sms-gateway/handlers/3rdparty.go (2)
50-73: Critical bug: nil pointer panic and auth routes incorrectly placed behind authentication.Two critical issues:
Nil pointer dereference at Line 60: The
authHandlerfield is never initialized in the constructor (see lines 75-86), so callingh.authHandler.Register()will panic.Authentication chicken-and-egg problem: The auth routes are registered at Line 60 after the authentication middleware is applied (lines 55-58). This means the
/tokenendpoint requires authentication to obtain an authentication token, which is logically impossible.The
/authroutes should be registered before theuserauthmiddleware is applied, typically right after the health handler registration.Apply this diff to fix both issues:
func (h *thirdPartyHandler) Register(router fiber.Router) { router = router.Group("/3rdparty/v1") h.healthHandler.Register(router) + h.authHandler.Register(router.Group("/auth")) router.Use( userauth.NewBasic(h.authSvc), userauth.UserRequired(), ) - h.authHandler.Register(router.Group("/auth")) - h.messagesHandler.Register(router.Group("/message")) // TODO: remove after 2025-12-31
75-86: Critical bug: authHandler field not initialized.The constructor receives
params.AuthHandlerbut never assigns it to theauthHandlerfield. This will cause a nil pointer dereference panic whenRegister()is called.Apply this diff to fix:
func newThirdPartyHandler(params ThirdPartyHandlerParams) *thirdPartyHandler { return &thirdPartyHandler{ Handler: base.Handler{Logger: params.Logger.Named("ThirdPartyHandler"), Validator: params.Validator}, healthHandler: params.HealthHandler, messagesHandler: params.MessagesHandler, webhooksHandler: params.WebhooksHandler, devicesHandler: params.DevicesHandler, settingsHandler: params.SettingsHandler, logsHandler: params.LogsHandler, + authHandler: params.AuthHandler, authSvc: params.AuthSvc, } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
internal/sms-gateway/handlers/3rdparty.go(4 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/jwt/config.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/handlers/thirdparty/module.go
🧬 Code graph analysis (4)
internal/sms-gateway/handlers/thirdparty/module.go (2)
internal/sms-gateway/handlers/module.go (1)
Module(16-41)internal/sms-gateway/handlers/thirdparty/auth.go (1)
NewAuthHandler(14-21)
internal/sms-gateway/jwt/config.go (2)
internal/config/config.go (1)
Config(12-23)internal/sms-gateway/jwt/errors.go (1)
ErrInvalidConfig(8-8)
internal/sms-gateway/handlers/module.go (1)
internal/sms-gateway/handlers/thirdparty/module.go (1)
Module(8-16)
internal/sms-gateway/handlers/3rdparty.go (1)
internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(10-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Analyze (go)
🔇 Additional comments (12)
internal/sms-gateway/jwt/config.go (3)
1-6: LGTM!Package declaration and imports are appropriate. Both
fmtandtimeare used correctly throughout the file.
8-10: LGTM!The minimum secret length of 32 bytes (256 bits) is appropriate for JWT secrets and aligns with security best practices for HMAC-SHA256.
18-32: LGTM! Previous concern has been addressed.The validation logic is comprehensive and correct:
- Secret validation checks for both emptiness and minimum length
- TTL validation correctly rejects both zero and negative values (line 27:
c.TTL <= 0)This addresses the previous review comment about negative TTL values.
internal/sms-gateway/handlers/thirdparty/module.go (1)
8-16: LGTM!The module structure follows the established fx patterns in the codebase. The logger configuration and provider setup are correct.
internal/sms-gateway/handlers/module.go (2)
9-9: LGTM!Import correctly added to support the new thirdparty module registration.
40-40: LGTM!Module registration follows the established pattern and correctly wires the thirdparty module into the handlers module.
internal/sms-gateway/handlers/thirdparty/auth.go (3)
10-21: LGTM!The AuthHandler structure and constructor follow the established patterns in the codebase. The embedded base.Handler is correctly initialized with logger and validator dependencies.
23-26: LGTM!Route registration follows the standard pattern. The endpoints are appropriately defined for JWT token generation and revocation.
28-34: Stub implementation as expected for draft PR.The NotImplemented placeholders are appropriate for the current draft state. These handlers will need proper implementation before the PR is ready for merge.
internal/sms-gateway/handlers/3rdparty.go (3)
10-10: LGTM!Import correctly added for the thirdparty.AuthHandler dependency.
19-34: LGTM!The AuthHandler dependency is correctly added to the parameter struct for fx injection.
36-48: LGTM!The authHandler field is correctly added to the internal handler struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
internal/sms-gateway/jwt/service.go (2)
28-49: Consider unexported constructor or exported storage type.The exported
Newfunction accepts an unexported*revokedStorageparameter, making it unusable by external packages. IfNewis intended only for internal module wiring, consider making it unexported (new). Alternatively, if external packages should construct the service, export therevokedStoragetype or accept an interface.
98-101: Consider wrapping error for better context.Line 100 returns a generic
"invalid token"error. Consider usingfmt.Errorfto wrap or provide additional context about the validation failure, which aids debugging.- if !ok || !parsedToken.Valid { - return nil, fmt.Errorf("invalid token") - } + if !ok { + return nil, fmt.Errorf("invalid token claims type") + } + if !parsedToken.Valid { + return nil, fmt.Errorf("token validation failed") + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
api/requests.http(1 hunks)internal/sms-gateway/handlers/3rdparty.go(5 hunks)internal/sms-gateway/jwt/service.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/sms-gateway/jwt/service.go (3)
internal/sms-gateway/jwt/jwt.go (1)
Claims(5-10)internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/errors.go (3)
ErrInitFailed(7-7)ErrInvalidConfig(8-8)ErrTokenRevoked(9-9)
internal/sms-gateway/handlers/3rdparty.go (3)
internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(10-12)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/health.go (1)
HealthHandler(13-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (5)
internal/sms-gateway/handlers/3rdparty.go (3)
10-10: LGTM: AuthHandler integration follows existing patterns.The import, field declarations, and initialization of
AuthHandlerare consistent with how other handlers (messages, webhooks, devices, etc.) are integrated into the third-party handler.Also applies to: 28-28, 45-45, 84-84
60-60: Verify middleware ordering for auth endpoints.The auth routes are registered after the
userauth.NewBasicanduserauth.UserRequiredmiddleware (lines 55-58), which means Basic authentication is required to access/auth/tokenand/auth/token/revoke.This appears to implement a token exchange pattern where clients trade Basic credentials for JWT tokens. However, please verify:
- Token generation: Is Basic auth → JWT exchange the intended flow?
- Token revocation: Should the revoke endpoint accept a Bearer token in the Authorization header instead of requiring Basic auth?
Consider whether token revocation should be accessible with Bearer tokens:
// Potential alternative: separate middleware for revoke endpoint authRouter := router.Group("/auth") h.authHandler.Register(authRouter) // Register first // Then apply selective middleware within AuthHandler.Register()
77-77: Logger initialization is consistent with all other handlers.All main handlers in the module (3rdparty, upstream, mobile) initialize their loggers identically using
params.Loggerdirectly without a name qualifier. Sub-handlers appropriately useNamed()for distinguishing their logs. No inconsistency exists.api/requests.http (1)
195-208: These test requests are for unimplemented endpoints—revisit once handlers are complete.The auth endpoint handlers in
internal/sms-gateway/handlers/thirdparty/auth.gocurrently returnfiber.ErrNotImplemented(lines 28-30 and 32-34). The test requests inapi/requests.http(lines 195-208) are placeholders and cannot be meaningfully validated against non-existent logic.Once the
token()andtokenRevoke()handlers are implemented, return here to verify and update these test requests with appropriate sample request bodies based on the actual implementation requirements.internal/sms-gateway/jwt/service.go (1)
82-112: Strong security validation in ParseToken.The implementation correctly enforces expiration, validates the issuer, restricts signing methods to HS256 (preventing algorithm confusion attacks), and checks token revocation. These are essential security practices for JWT handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
internal/sms-gateway/jwt/revoked.go (1)
22-35: Revocation storage correctly uses cache TTL; consider clarifying naming
IsRevoked/Revokecorrectly treat presence in the cache as revocation and now apply a TTL viacache.WithTTL, which avoids unbounded growth in both Redis and in‑memory caches (where TTL is tracked by the app). Based on learnings.You might optionally rename the
tokenparameter (and related local naming) tojtiorkeyto better reflect that you’re storing the token ID, not the full JWT string.internal/sms-gateway/jwt/service.go (2)
51-76: Consider avoiding reliance on builtinminfor broader Go version support
ExpiresAtusesnow.Add(min(ttl, s.config.TTL)). If the project targets Go versions without the builtinminfortime.Duration, this will not compile; a small local helper keeps behavior explicit and portable:func minDuration(a, b time.Duration) time.Duration { if a < b { return a } return b }and then:
ExpiresAt: jwt.NewNumericDate(now.Add(minDuration(ttl, s.config.TTL))),Functionally the capping behavior (never exceeding configured TTL) is sound; this is mainly about portability and clarity.
90-124: Normalize ParseToken error mapping around ErrInvalidToken / ErrUnexpectedSigningMethodRight now:
- Any
jwt.ParseWithClaimserror is wrapped asfmt.Errorf("failed to parse token: %w", err).ErrInvalidTokenis only returned when the claims type assertion fails orparsedToken.Validis false.ErrUnexpectedSigningMethodis defined but not used; signing-method mismatches will surface as generic parse errors.For callers, it may be more convenient if all “token is syntactically/cryptographically/temporally invalid” cases (bad signature, expired, issuer mismatch, invalid method, etc.) are surfaced under
ErrInvalidToken, with the underlying jwt error still wrapped for logging/debugging. For example:parsedToken, err := jwt.ParseWithClaims(/* ... */) if err != nil { return nil, fmt.Errorf("%w: %v", ErrInvalidToken, err) } claims, ok := parsedToken.Claims.(*Claims) if !ok || !parsedToken.Valid { return nil, ErrInvalidToken }Optionally, if you want to distinguish signing‑method issues, you could inspect the error returned by the library and wrap it with
ErrUnexpectedSigningMethod. This would give a clearer and more stable public error contract while still usingErrTokenRevokedfor the revocation path.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/revoked.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
internal/sms-gateway/jwt/revoked.go
🧬 Code graph analysis (4)
internal/sms-gateway/jwt/module.go (5)
internal/sms-gateway/app.go (1)
Module(34-58)internal/sms-gateway/cache/factory.go (1)
Factory(16-18)internal/sms-gateway/jwt/service.go (2)
New(28-49)Service(14-18)internal/sms-gateway/jwt/revoked.go (2)
NewRevokedStorage(16-20)RevokedStorage(12-14)internal/sms-gateway/jwt/config.go (1)
Config(12-16)
internal/sms-gateway/jwt/errors.go (1)
internal/sms-gateway/jwt/service.go (1)
New(28-49)
internal/sms-gateway/jwt/service.go (4)
internal/sms-gateway/jwt/jwt.go (1)
Claims(5-10)internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/revoked.go (1)
RevokedStorage(12-14)internal/sms-gateway/jwt/errors.go (4)
ErrInitFailed(7-7)ErrInvalidConfig(8-8)ErrInvalidToken(9-9)ErrTokenRevoked(10-10)
internal/sms-gateway/jwt/revoked.go (3)
internal/config/config.go (1)
Cache(83-85)pkg/cache/errors.go (1)
ErrKeyNotFound(7-7)pkg/cache/options.go (1)
WithTTL(22-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (3)
internal/sms-gateway/jwt/errors.go (1)
5-12: Centralized JWT error definitions look goodError values are clear, specific, and align with the usages in the service (config/init/token/revocation), which should keep downstream error handling consistent.
internal/sms-gateway/jwt/module.go (1)
9-24: FX wiring and conditional disablement are coherentThe module cleanly encapsulates its own cache and
RevokedStorageas private providers and exposes onlyService, with a straightforward disabled path whenConfig.Secretis empty. This keeps JWT concerns modular without leaking cache internals.internal/sms-gateway/jwt/service.go (1)
28-48: Constructor enforces config and dependencies appropriately
NewvalidatesConfig, rejects a nilRevokedStorage, and wires anidFactoryfrom nanoid, so misconfigurations or missing dependencies fail fast at startup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
internal/sms-gateway/jwt/revoked.go (3)
12-20: Add godoc comments for exported types and methods.The exported
RevokedStoragetype andNewRevokedStorageconstructor lack documentation. Adding godoc comments will improve API discoverability and maintainability.Apply this diff to add documentation:
+// RevokedStorage manages revoked JWT tokens using a cache backend. +// Revoked tokens are stored with a TTL matching their expiration time +// to prevent unbounded cache growth. type RevokedStorage struct { storage cache.Cache } +// NewRevokedStorage creates a new RevokedStorage backed by the given cache. func NewRevokedStorage(storage cache.Cache) *RevokedStorage { return &RevokedStorage{ storage: storage, } }Optionally, add a nil check for defensive coding:
func NewRevokedStorage(storage cache.Cache) *RevokedStorage { + if storage == nil { + panic("storage cannot be nil") + } return &RevokedStorage{ storage: storage, } }
22-32: Add godoc comment; implementation is correct.The
IsRevokedmethod correctly handles all three cases: token not found (not revoked), token found (revoked), and errors. Error wrapping provides helpful context.Add documentation:
+// IsRevoked checks whether a JWT with the given ID has been revoked. +// Returns (false, nil) if the token is not revoked, (true, nil) if revoked, +// or (false, error) on lookup failure. func (r *RevokedStorage) IsRevoked(ctx context.Context, jti string) (bool, error) { _, err := r.storage.Get(ctx, jti) if errors.Is(err, cache.ErrKeyNotFound) {
34-36: TTL implementation looks good; consider error wrapping for consistency.The addition of the
ttlparameter andcache.WithTTL(ttl)correctly addresses the previous concern about unbounded cache growth. Revoked tokens will now expire automatically.Add godoc and optionally wrap errors for consistency with
IsRevoked:+// Revoke marks a JWT with the given ID as revoked until the specified TTL expires. +// The TTL should typically match the token's remaining lifetime. func (r *RevokedStorage) Revoke(ctx context.Context, jti string, ttl time.Duration) error { - return r.storage.Set(ctx, jti, nil, cache.WithTTL(ttl)) + if err := r.storage.Set(ctx, jti, nil, cache.WithTTL(ttl)); err != nil { + return fmt.Errorf("failed to revoke token: %w", err) + } + return nil }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/sms-gateway/jwt/revoked.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
internal/sms-gateway/jwt/revoked.go
🧬 Code graph analysis (1)
internal/sms-gateway/jwt/revoked.go (2)
pkg/cache/errors.go (1)
ErrKeyNotFound(7-7)pkg/cache/options.go (1)
WithTTL(22-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Analyze (go)
9673e3c to
9d5cf91
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
configs/config.example.yml (1)
41-44: Document that empty secret disables JWT.The
jwt.secretfield is empty by default, which will cause the JWT service to operate in disabled mode (returningErrDisabledfor all operations). Consider adding a comment to make this behavior explicit for users configuring the system.Apply this diff to clarify the behavior:
jwt: - secret: # jwt secret [JWT__SECRET] + secret: # jwt secret (leave empty to disable JWT functionality) [JWT__SECRET] ttl: 24h # jwt ttl [JWT__TTL] issuer: # jwt issuer [JWT__ISSUER]internal/sms-gateway/handlers/thirdparty/auth.go (1)
70-72: Token revocation endpoint is not implemented.The
deleteTokenhandler returnsErrNotImplemented. According to the JWT service interface, this should calljwtSvc.RevokeToken(ctx, jti)to revoke the token.Would you like me to generate the implementation? The basic logic would be:
func (h *AuthHandler) deleteToken(user users.User, c *fiber.Ctx) error { - return fiber.ErrNotImplemented + jti := c.Params("jti") + if jti == "" { + return fiber.NewError(fiber.StatusBadRequest, "JTI is required") + } + + if err := h.jwtSvc.RevokeToken(c.Context(), jti); err != nil { + return err + } + + return c.SendStatus(fiber.StatusNoContent) }internal/sms-gateway/users/repository.go (1)
43-45: Consider wrapping error for consistency.Unlike
GetByIDandInsert, this method returns the raw GORM error. For consistency, consider wrapping it:func (r *repository) UpdatePassword(ID string, passwordHash string) error { - return r.db.Model((*User)(nil)).Where("id = ?", ID).Update("password_hash", passwordHash).Error + if err := r.db.Model((*User)(nil)).Where("id = ?", ID).Update("password_hash", passwordHash).Error; err != nil { + return fmt.Errorf("can't update password: %w", err) + } + return nil }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (37)
api/requests.http(1 hunks)configs/config.example.yml(1 hunks)go.mod(1 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(2 hunks)internal/sms-gateway/handlers/3rdparty.go(5 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(3 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(5 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(3 hunks)internal/sms-gateway/handlers/mobile.go(3 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(4 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(4 hunks)internal/sms-gateway/jwt/config.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/revoked.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(2 hunks)internal/sms-gateway/modules/auth/service.go(8 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/users/domain.go(1 hunks)internal/sms-gateway/users/errors.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)
💤 Files with no reviewable changes (1)
- internal/sms-gateway/models/models.go
🚧 Files skipped from review as they are similar to previous changes (17)
- internal/config/module.go
- internal/sms-gateway/users/errors.go
- internal/sms-gateway/users/models.go
- go.mod
- internal/sms-gateway/handlers/settings/3rdparty.go
- internal/sms-gateway/modules/webhooks/models.go
- internal/sms-gateway/jwt/jwt.go
- internal/sms-gateway/users/module.go
- internal/sms-gateway/handlers/messages/3rdparty.go
- internal/sms-gateway/handlers/devices/3rdparty.go
- internal/sms-gateway/jwt/errors.go
- internal/sms-gateway/modules/settings/models.go
- internal/sms-gateway/modules/messages/service.go
- api/requests.http
- internal/sms-gateway/jwt/config.go
- internal/sms-gateway/handlers/thirdparty/module.go
- internal/sms-gateway/handlers/module.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
internal/sms-gateway/jwt/revoked.go
🧬 Code graph analysis (16)
internal/sms-gateway/jwt/module.go (7)
internal/config/module.go (1)
Module(23-135)internal/sms-gateway/app.go (1)
Module(34-58)internal/sms-gateway/cache/factory.go (1)
Factory(16-18)internal/sms-gateway/jwt/service.go (1)
New(22-43)internal/sms-gateway/jwt/revoked.go (2)
NewRevokedStorage(16-20)RevokedStorage(12-14)internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)
internal/sms-gateway/jwt/disabled.go (3)
internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/config/types.go (1)
Duration(11-11)internal/sms-gateway/jwt/errors.go (1)
ErrDisabled(6-6)
internal/sms-gateway/app.go (2)
internal/sms-gateway/jwt/module.go (1)
Module(9-25)internal/config/module.go (1)
Module(23-135)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/users/models.go (1)
User(10-16)
internal/sms-gateway/jwt/revoked.go (2)
pkg/cache/errors.go (1)
ErrKeyNotFound(7-7)pkg/cache/options.go (1)
WithTTL(22-30)
internal/sms-gateway/handlers/3rdparty.go (3)
internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(15-19)internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/health.go (1)
HealthHandler(13-17)
internal/sms-gateway/handlers/logs/3rdparty.go (1)
internal/sms-gateway/users/models.go (1)
User(10-16)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
internal/sms-gateway/users/models.go (1)
User(10-16)
internal/config/config.go (1)
internal/config/types.go (1)
Duration(11-11)
internal/sms-gateway/handlers/thirdparty/auth.go (3)
internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(121-125)internal/sms-gateway/users/models.go (1)
User(10-16)
internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
internal/sms-gateway/users/models.go (1)
User(10-16)
internal/sms-gateway/jwt/service.go (4)
internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/revoked.go (1)
RevokedStorage(12-14)internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (4)
ErrInitFailed(7-7)ErrInvalidConfig(8-8)ErrInvalidToken(9-9)ErrTokenRevoked(10-10)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (1)
Device(17-28)
internal/sms-gateway/modules/auth/service.go (1)
internal/sms-gateway/users/models.go (1)
User(10-16)
internal/sms-gateway/modules/auth/repository.go (1)
internal/sms-gateway/users/models.go (1)
User(10-16)
internal/sms-gateway/users/repository.go (2)
internal/sms-gateway/users/models.go (1)
User(10-16)internal/sms-gateway/users/errors.go (1)
ErrNotFound(6-6)
🪛 GitHub Actions: Go
internal/sms-gateway/users/repository.go
[error] 10-10: golangci-lint: type repository is unused (unused).
🪛 GitHub Check: Lint
internal/sms-gateway/users/repository.go
[failure] 43-43:
func (*repository).UpdatePassword is unused (unused)
[failure] 35-35:
func (*repository).Insert is unused (unused)
[failure] 22-22:
func (*repository).GetByID is unused (unused)
[failure] 15-15:
func newRepository is unused (unused)
[failure] 10-10:
type repository is unused (unused)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build / Docker image (linux/arm64)
- GitHub Check: Build / Docker image (linux/amd64)
🔇 Additional comments (28)
internal/sms-gateway/models/migration.go (1)
12-13: Confirm users migrations are now handled outsidemodels.MigrateRestricting
AutoMigratehere toDeviceonly matches the move ofUserinto theuserspackage, so this function’s scope is now clear and focused. Just make sure:
- There is a corresponding
users.Migrate(or similar) that migrates theusersschema.- Whatever initializes the DB now calls both migration entry points in a deterministic order (typically users before devices, if you ever add FKs).
If both are true, this change looks good.
internal/sms-gateway/jwt/disabled.go (1)
8-28: LGTM! Clean null object pattern implementation.The disabled service implementation correctly returns
ErrDisabledfor all JWT operations when the service is not configured. This provides graceful degradation whenconfig.Secretis empty.internal/config/types.go (1)
11-48: LGTM! Well-implemented Duration type with proper nil-safety.The custom Duration type correctly implements YAML and text unmarshaling with nil-safe accessors. Error wrapping is appropriate, and compile-time interface checks ensure conformance.
internal/sms-gateway/handlers/3rdparty.go (2)
10-10: LGTM! AuthHandler integration is correct.The third-party authentication handler is properly wired through FX dependency injection and registered at the
/authroute group.Also applies to: 28-28, 45-45, 60-60, 84-84
77-77: Verify logger naming change is intentional.The logger no longer uses
.Named("ThirdPartyHandler"), which changes the logger identifier from "ThirdPartyHandler" to the parent logger name. This may affect log filtering and observability. Please confirm this change is intentional.Previous pattern (based on other handlers):
Logger: params.Logger.Named("ThirdPartyHandler")Current pattern:
Logger: params.Loggerinternal/sms-gateway/users/domain.go (1)
1-1: Question: Purpose of empty domain.go file?This file contains only a package declaration with no types, functions, or imports. Is this a placeholder for future domain logic, or was content intended to be added in this PR?
internal/sms-gateway/handlers/mobile.go (1)
19-19: LGTM! Clean refactoring to use the users package.The User type references have been correctly updated from
models.Usertousers.User, aligning with the new package structure. No logic changes were introduced.Also applies to: 104-104, 179-179
internal/sms-gateway/jwt/module.go (1)
9-25: LGTM! Clean FX module with conditional service activation.The JWT module correctly provides a disabled service when
config.Secretis empty (line 18) and a fully functional service otherwise. The private cache and revoked storage are properly scoped within the module.internal/sms-gateway/app.go (1)
10-10: LGTM! JWT module correctly integrated into the application.The JWT module is properly imported and registered in the FX module list, following the established pattern for other modules.
Also applies to: 57-57
internal/sms-gateway/handlers/logs/3rdparty.go (1)
6-6: LGTM! Clean refactor to the new users package.The type migration from
models.Usertousers.Useris consistent with the broader refactoring across the codebase.Also applies to: 38-38
internal/sms-gateway/modules/auth/repository.go (1)
4-4: LGTM! Consistent type migration across repository methods.All repository methods have been updated to use
users.Userconsistently, aligning with the new users package structure.Also applies to: 19-36
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
8-8: LGTM! Middleware properly updated to use new users package.The middleware functions and comments have been consistently updated to use
users.Userthroughout.Also applies to: 95-99, 121-121
internal/sms-gateway/jwt/revoked.go (2)
22-32: LGTM! IsRevoked implementation is correct.The logic properly distinguishes between not-found (not revoked) and error cases, with appropriate error wrapping.
34-36: LGTM! Revoke correctly includes TTL to prevent unbounded cache growth.The implementation now properly sets a TTL for revoked tokens, addressing the concern from the previous review. This ensures that revoked tokens are automatically cleaned up after the JWT expires.
internal/sms-gateway/handlers/thirdparty/auth.go (2)
39-42: LGTM! Token request validation is properly configured.The validation rules correctly enforce that at least one scope is provided, with TTL being optional.
51-68: LGTM! Token generation handler is correctly implemented.The handler properly validates input, generates the token, and returns a 201 status with the token details.
internal/sms-gateway/users/repository.go (2)
22-33: LGTM! GetByID properly handles errors and maps GORM's not found error.The implementation correctly uses
Takefor single-record queries and properly wraps errors.
35-41: LGTM! Insert method is correctly implemented.The method properly creates the user record with appropriate error wrapping.
internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
10-10: LGTM! Webhooks handler consistently updated to use new users package.All three handler methods (
get,post,delete) have been consistently updated to useusers.User.Also applies to: 43-43, 66-66, 96-96
internal/config/config.go (1)
91-95: The codebase properly validates empty JWT secrets. TheConfig.Validate()method ininternal/sms-gateway/jwt/config.go(line 19) explicitly checks for empty secrets and returns an error, with an additional minimum length requirement (line 23). Additionally, the module initialization ininternal/sms-gateway/jwt/module.go(line 18) provides a defensive check that returns a disabled service if the secret is empty. No action required.internal/sms-gateway/jwt/service.go (5)
22-43: LGTM! Constructor properly validates dependencies.The constructor correctly validates the config, ensures revoked storage is not nil, and initializes the nanoid-based ID factory with appropriate error handling.
46-48: Excellent! UserID validation now implemented.The addition of userID validation prevents generating JWT tokens with an empty Subject claim, addressing the concern from the previous review.
45-82: Verify TTL capping behavior is documented for API consumers.Line 69 caps the token expiry at
min(ttl, s.config.TTL), which enforces a maximum TTL from configuration. When a caller requests a TTL larger than the configured maximum, it will be silently reduced. This is a reasonable design to prevent excessively long-lived tokens, but ensure this behavior is documented in the API so callers understand their requested TTL may not be honored.
84-114: LGTM! ParseToken has comprehensive validation.The token parsing includes proper security validations (expiration, issuer, signing method) and correctly integrates revocation checks. The error handling appropriately wraps errors for context while returning sentinel errors for revoked/invalid tokens.
116-118: LGTM! Clean delegation to revocation storage.The method appropriately delegates to the revocation storage layer with the configured TTL, ensuring revocation records are retained for the appropriate duration.
internal/sms-gateway/modules/auth/service.go (3)
15-15: LGTM! Import added for user type migration.The addition of the
userspackage import supports the migration of the User type frommodelstousers, while themodelsimport remains for Device and other types.
45-45: LGTM! Cache type updated consistently.The
usersCachefield type and initialization correctly reflect the migration tousers.User, maintaining the existing cache configuration.Also applies to: 67-67
99-114: Type migration verified successfully—all call sites properly updated.All public methods in the auth service that return or accept
users.Userhave been consistently updated. Verification confirms:
RegisterUserreturn values assigned tousers.Uservariables (mobile.go:104)RegisterDevicereceivesusers.Userparameters correctly (mobile.go:123)AuthorizeUserreturn values assigned tousers.Uservariables (userauth.go:48)AuthorizeUserByCodereturn values assigned tousers.Uservariables (userauth.go:75)- No remaining
models.Userreferences outside the auth moduleThe type migration is complete and consistent throughout the codebase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
configs/config.example.yml(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- configs/config.example.yml
- internal/sms-gateway/users/module.go
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/users/repository.go (2)
internal/sms-gateway/users/models.go (1)
User(10-16)internal/sms-gateway/users/errors.go (1)
ErrNotFound(6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (3)
internal/sms-gateway/users/repository.go (3)
21-33: Good error handling for record not found.The method correctly distinguishes between "record not found" and other database errors, returning the appropriate custom error.
35-41: LGTM!The insert operation is correctly implemented with proper error wrapping.
10-51: Repository is properly provided to the DI container—concern resolved.The repository is correctly registered in
users/module.goat line 13 viafx.Provide(newRepository, fx.Private), which addresses the previous review concern. Thefx.Privatescope appropriately restricts visibility to the module while making it available for injection to internal consumers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (6)
api/mobile.http (1)
71-72: Test credentials in version control require caution.The static analysis tool flagged these values as potential generic API keys. While these are clearly placeholder test credentials (not production secrets), it's generally best practice to avoid committing any credentials—even test ones—to version control.
Consider moving these to environment variables or a gitignored
.http.localfile that developers can customize locally without risk of accidental exposure.internal/sms-gateway/handlers/3rdparty.go (1)
36-83:authSvcis unused; confirm intended auth coverage for/3rdparty/v1/auth
- The
authSvc *auth.Servicefield (and corresponding constructor parameter) is no longer used anywhere in this handler. It can be safely dropped to keep the dependency surface clean.- With
router.Use(userauth.NewBasic(h.usersSvc), userauth.UserRequired())applied beforeh.authHandler.Register(router.Group("/auth")), all/3rdparty/v1/auth/**routes now require Basic auth as well. That’s probably what you want (e.g. exchanging Basic for JWT), but it’s a behavior change worth double‑checking against the API contract.type thirdPartyHandler struct { base.Handler - authSvc *auth.Service usersSvc *users.Service @@ -func newThirdPartyHandler( - authSvc *auth.Service, +func newThirdPartyHandler( usersSvc *users.Service, @@ - authSvc: authSvc, usersSvc: usersSvc,internal/sms-gateway/users/service.go (1)
33-108: Don’t let cache failures block password changesThe service logic looks good overall (bcrypt usage, error wrapping, and login cache behavior). One place that’s a bit harsh is
ChangePassword:if err := s.cache.Delete(ctx, username, currentPassword); err != nil { return err }Given that
Loginalready treats the cache as best‑effort (miss or failure just falls back to DB) andSetonly logs on error, making password changes fail when the cache is unavailable is inconsistent and can hurt reliability.Consider aligning it with
Login:- if err := s.cache.Delete(ctx, username, currentPassword); err != nil { - return err - } + if err := s.cache.Delete(ctx, username, currentPassword); err != nil { + s.logger.Error("failed to delete login cache entry", zap.String("username", username), zap.Error(err)) + }You may also want to be aware that
Create’sExists→Insertsequence is still racy under concurrent creation attempts; the DB primary key will save you, but the resulting error won’t beErrExists.internal/sms-gateway/modules/auth/service.go (1)
24-36: Auth service integration withusers.Serviceand code-based auth looks sane
- Injecting
usersSvc *users.Serviceand delegatingRegisterDeviceandAuthorizeUserByCodeto it is a clean separation of concerns.AuthorizeUserByCode’s flow (codesCache.GetAndDelete→usersSvc.GetByUsername) is straightforward; with the userauth middleware storing a value into Locals, types now line up end‑to‑end.No issues from these changes; just consider renaming
userIDinGenerateUserCode/codesCachetousernamefor clarity, since it’s passed toGetByUsername.Also applies to: 38-59, 90-97, 133-145
internal/sms-gateway/handlers/mobile.go (2)
28-41: Remove commented-out code.The old fx.In dependency injection structure is no longer used. Consider removing this commented code to improve maintainability.
142-144: Add defensive nil check afterGetUser.While
HasUserchecksGetUser(c) != nil, adding an explicit nil check improves defensive coding and prevents potential panics if the middleware implementation changes.Apply this diff:
if userauth.HasUser(c) { user = userauth.GetUser(c) + if user == nil { + return fmt.Errorf("user authentication inconsistency") + } username = user.ID
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
api/mobile.http(1 hunks)api/requests.http(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/users/cache.go(1 hunks)internal/sms-gateway/users/domain.go(1 hunks)internal/sms-gateway/users/errors.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)internal/sms-gateway/users/service.go(1 hunks)pkg/cache/typed.go(1 hunks)
💤 Files with no reviewable changes (2)
- internal/sms-gateway/modules/auth/module.go
- internal/sms-gateway/modules/auth/repository.go
🚧 Files skipped from review as they are similar to previous changes (5)
- internal/sms-gateway/users/module.go
- internal/sms-gateway/users/errors.go
- api/requests.http
- internal/sms-gateway/users/domain.go
- internal/sms-gateway/users/repository.go
🧰 Additional context used
🧬 Code graph analysis (9)
internal/sms-gateway/users/cache.go (3)
pkg/cache/typed.go (2)
Typed(13-15)NewTyped(17-21)internal/sms-gateway/users/domain.go (1)
User(5-10)pkg/cache/options.go (2)
AndSetTTL(64-68)WithTTL(22-30)
internal/sms-gateway/users/service.go (3)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/users/errors.go (1)
ErrExists(7-7)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)
internal/sms-gateway/app.go (3)
internal/sms-gateway/users/module.go (1)
Module(12-27)internal/sms-gateway/jwt/module.go (1)
Module(9-25)internal/config/module.go (1)
Module(23-135)
pkg/cache/typed.go (1)
pkg/cache/options.go (1)
GetOption(48-48)
internal/sms-gateway/users/models.go (3)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-15)internal/sms-gateway/models/migration.go (1)
Migrate(12-14)internal/sms-gateway/modules/settings/models.go (1)
Migrate(20-25)
internal/sms-gateway/handlers/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(11-17)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
HasUser(89-91)GetUser(96-103)NewBasic(20-57)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
internal/sms-gateway/users/service.go (1)
Service(11-17)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/handlers/3rdparty.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(11-17)internal/sms-gateway/modules/auth/service.go (1)
Service(24-36)internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(15-19)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
NewBasic(20-57)UserRequired(108-116)
internal/sms-gateway/modules/auth/service.go (4)
internal/sms-gateway/users/service.go (1)
Service(11-17)internal/sms-gateway/online/service.go (1)
Service(14-17)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/models/models.go (1)
Device(17-28)
🪛 Gitleaks (8.29.0)
api/mobile.http
[high] 71-71: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 72-72: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (3)
internal/sms-gateway/users/models.go (1)
10-27: User persistence model & migration look consistentSchema and
Migrateimplementation match existing patterns (soft-deletable model, explicit table name, wrapped migration error). No issues from this diff alone.internal/sms-gateway/users/cache.go (1)
14-79: Login cache behavior is reasonable given the TTL and keying strategyUsing a 1‑hour TTL and deriving the cache key as
sha256(username || 0x00 || password)keeps only successful logins cached and avoids storing raw credentials. With the Typed cache fixed, Get/Set/Delete here look correct and errors are surfaced cleanly to the service layer.internal/sms-gateway/app.go (1)
7-60: JWT and users modules are wired correctly into the Fx graphAdding
users.Module()andjwt.Module()to the top‑levelModuleis consistent with how other modules are registered, and should give both services access to config/logger/cache as intended. No issues from this diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/cache/redis.go (1)
41-44: Fix inconsistent HEXPIRE syntax on line 44.HEXPIRE requires
FIELDS count fieldsyntax. Line 40 correctly uses'FIELDS', '1', field, but line 44 omits the FIELDS keyword. Line 44 must be updated to match:- redis.call('HExpire', KEYS[1], newTtl, field) + redis.call('HExpire', KEYS[1], newTtl, 'FIELDS', '1', field)
♻️ Duplicate comments (2)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
128-131: GuardWithUseragainst missing user for a clearer failure mode
WithUsercurrently does:return handler(*GetUser(c), c)If it’s ever used without a preceding auth middleware (or if that middleware fails to set the user), this will panic with a nil dereference, which is harder to diagnose.
You already document that it will panic when the user is missing; consider adding an explicit check to produce a clearer panic message:
func WithUser(handler func(users.User, *fiber.Ctx) error) fiber.Handler { return func(c *fiber.Ctx) error { - return handler(*GetUser(c), c) + user := GetUser(c) + if user == nil { + panic("userauth.WithUser called without an authenticated user in context") + } + return handler(*user, c) } }This keeps the contract (“panics if user is missing”) but makes debugging much easier if the middleware chain is misconfigured.
internal/sms-gateway/handlers/mobile.go (1)
43-74: Handlenanoid.Standarderror to avoid nilidGenpanicsAt Line 56 you ignore the error from
nanoid.Standard(21):idGen, _ := nanoid.Standard(21)If initialization fails,
idGenwill be nil and any laterh.idGen()call will panic at runtime. This was already flagged in a previous review; it’s still a critical correctness issue.A minimal fix is to fail fast during handler construction:
func newMobileHandler( @@ - idGen, _ := nanoid.Standard(21) + idGen, err := nanoid.Standard(21) + if err != nil { + panic(fmt.Sprintf("failed to initialize ID generator: %v", err)) + }This guarantees
idGenis never nil in live handlers.
🧹 Nitpick comments (4)
internal/sms-gateway/users/service.go (1)
96-112: Avoid failing password changes on missing/expired cache entries
ChangePasswordcurrently treats any error froms.cache.Deleteas fatal. If the login entry has already expired or was never cached,Deletewill likely returncache.ErrKeyNotFound, which would cause a valid password change to fail purely due to cache state, even though you’ve already authenticated viaLogin.You already treat
cache.ErrKeyNotFoundas benign inLogin; consider doing the same here so cache misses don’t block updates:func (s *Service) ChangePassword(ctx context.Context, username, currentPassword, newPassword string) error { @@ - if err := s.cache.Delete(ctx, username, currentPassword); err != nil { - return err - } + if err := s.cache.Delete(ctx, username, currentPassword); err != nil { + if !errors.Is(err, cache.ErrKeyNotFound) { + return err + } + }The rest of the flow (authenticate via
Login, hash new password, thenUpdatePassword) looks good.internal/sms-gateway/handlers/mobile.go (2)
114-152: Clarify response semantics for existing vs new users inpostDeviceThe flow for creating/registering a device with the new
users.Servicelooks consistent (reusing an authenticated user when present and creating a new one otherwise). One detail to double‑check is the response payload:
- For new users, you set both
LoginandPassword.- For already authenticated users, you set
Login = usernamebut leavePasswordempty.The docstring says “Returns user credentials only for new users”, which might suggest that both fields should be omitted/empty for existing users, not just
Password. If clients interpret any non‑emptyLoginas “new credentials”, this could be confusing.If the intent is truly “credentials only for new users”, consider leaving
Loginempty (or documenting that onlyPasswordis conditional) in the authenticated‑user branch.
223-236: Differentiate invalid password from internal failures inchangePassword
changePasswordcurrently treats any error fromh.usersSvc.ChangePasswordas “Invalid current password” and returns401:if err := h.usersSvc.ChangePassword(...); err != nil { h.Logger.Error("failed to change password", zap.Error(err)) return fiber.NewError(fiber.StatusUnauthorized, "Invalid current password") }But
ChangePasswordcan fail for reasons other than a bad current password (e.g. DB error, cache backend failure, hashing error). Mapping all of those to401hides server‑side issues and misleads clients.Consider distinguishing:
- Invalid current password →
401 Unauthorizedwith the current message.- Other errors → propagate or wrap as
500 Internal Server Error.You can do this by checking
errors.Is(err, crypto.ErrPasswordInvalid)(the error returned fromCompareBCryptHash) versus other error types.internal/sms-gateway/handlers/3rdparty.go (1)
64-87: Confirm that protecting/3rdparty/v1/authwith Basic auth is intentionalIn
Registeryou add:router.Use( userauth.NewBasic(h.usersSvc), userauth.UserRequired(), ) h.authHandler.Register(router.Group("/auth"))Given this order, the
/3rdparty/v1/authendpoints registered byauthHandlerwill also go throughNewBasic+UserRequired, effectively requiring basic authentication for the auth routes themselves.If the design is “exchange Basic credentials for JWT tokens” then this is fine; if the
/authendpoints are meant to be usable without prior Basic auth (e.g. by presenting some other credential or for login itself), you may want to:
- Apply the Basic middleware only to specific sub‑groups (messages/devices/settings/etc.), or
- Register
/authbefore callingrouter.Use.Please verify that the current middleware scope matches the desired API behavior.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/users/service.go(1 hunks)pkg/cache/memory.go(2 hunks)pkg/cache/redis.go(1 hunks)pkg/cache/typed.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- pkg/cache/typed.go
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/redis.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
🧬 Code graph analysis (4)
internal/sms-gateway/handlers/3rdparty.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(15-19)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
NewBasic(20-57)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/users/service.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/users/errors.go (1)
ErrExists(7-7)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)pkg/cache/errors.go (1)
ErrKeyNotFound(7-7)
internal/sms-gateway/handlers/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
GetUser(100-107)NewBasic(20-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (3)
pkg/cache/memory.go (1)
30-30: LGTM! Good refactoring.The rename from
newItemtonewMemoryItemimproves clarity by making the function's purpose more explicit.Also applies to: 155-155
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
15-54: User storage in context is now consistent across Basic and Code authThe switch to
users.ServiceinNewBasicplus the newsetUser/GetUserpairing makes the user storage consistent:
- Both
NewBasicandNewCodenow store ausers.Uservalue viasetUser.GetUsertype‑asserts tousers.Userand returns a pointer, andHasUserjust checks for non‑nil.This removes the earlier pointer/value mismatch between Basic and Code flows and should make
GetUser/HasUserreliable for all callers.Also applies to: 80-107
internal/sms-gateway/handlers/3rdparty.go (1)
18-62: Constructor and dependency wiring for third‑party handler look consistentThe new
thirdPartyHandlershape andnewThirdPartyHandlerconstructor wire in:
usersSvc *users.Servicefor basic auth viauserauth.NewBasic.authHandler *thirdparty.AuthHandlerfor JWT/3rd‑party auth endpoints.base.Handlerwithloggerandvalidator.The explicit constructor keeps dependencies clear and avoids fx‑specific parameter structs here. No issues from a correctness or maintainability standpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/sms-gateway/handlers/devices/3rdparty.go (1)
46-89: Delete route path is missing required leading slash—this is a functional bugFiber's grouped routers require leading slashes in route paths, as routes are flattened to their full path.
Current code at line 88:
router.Delete(":id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove))This should be:
router.Delete("/:id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove))This aligns with the webhooks handler (line 113 of
internal/sms-gateway/handlers/webhooks/3rdparty.go) and the documented path/3rdparty/v1/devices/{id}.
♻️ Duplicate comments (1)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
16-133: User storage bug is fixed; consider makingWithUser’s failure mode clearerNice improvement: both
NewBasicandNewCodenow callSetUser(c, *user), which matchesGetUser’s.(users.User)assertion, so the previous pointer/value mismatch is resolved.HasUsernow correctly delegates toGetUser.One remaining sharp edge is
WithUser:func WithUser(handler func(users.User, *fiber.Ctx) error) fiber.Handler { return func(c *fiber.Ctx) error { return handler(*GetUser(c), c) } }If a route accidentally uses
WithUserwithout any prior auth middleware setting the user,GetUser(c)returns nil and this will panic with a generic nil‑pointer deref, even though the comment says “it will panic”.For a clearer failure mode, you could guard this:
func WithUser(handler func(users.User, *fiber.Ctx) error) fiber.Handler { return func(c *fiber.Ctx) error { - return handler(*GetUser(c), c) + user := GetUser(c) + if user == nil { + panic("WithUser called without authenticated user in context") + } + return handler(*user, c) } }This keeps the contract (it still panics) but makes misconfiguration easier to diagnose.
🧹 Nitpick comments (3)
internal/sms-gateway/handlers/messages/permissions.go (1)
3-7: LGTM! Consider adding documentation.The permission scope constants are well-named and follow the standard
resource:actionpattern, which aligns with JWT/OAuth2 best practices. The granularity (send, read, list) appropriately separates concerns.Consider adding godoc comments to clarify each scope's purpose:
+// ScopeSend grants permission to send new messages. const ( ScopeSend = "messages:send" + // ScopeRead grants permission to read individual messages. ScopeRead = "messages:read" + // ScopeList grants permission to list messages. ScopeList = "messages:list" )internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
28-31: Consider adding error logging for debugging.When user lookup fails, the middleware returns a generic 401 without logging. While this is good for security (not leaking information), consider adding debug-level logging to help diagnose authentication issues during development.
Example enhancement:
user, err := usersSvc.GetByUsername(claims.UserID) if err != nil { + // Optional: Add debug logging here + // logger.Debug("JWT auth failed: user not found", zap.String("userID", claims.UserID)) return fiber.ErrUnauthorized }internal/sms-gateway/handlers/3rdparty.go (1)
35-67: Consider a parameter struct if constructor complexity grows.The constructor currently accepts 8 parameters, which is at the upper limit of what's manageable. While acceptable for explicit dependency injection, consider introducing a parameter struct if more dependencies are added in the future.
Example refactor:
type ThirdPartyHandlerParams struct { UsersSvc *users.Service JwtService jwt.Service HealthHandler *HealthHandler MessagesHandler *messages.ThirdPartyController // ... other handlers Logger *zap.Logger Validator *validator.Validate } func newThirdPartyHandler(params ThirdPartyHandlerParams) *thirdPartyHandler { // ... }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
api/requests.http(4 hunks)cmd/sms-gateway/main.go(1 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(6 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(3 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(10 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(8 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(8 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/openapi/docs.go(20 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/sms-gateway/handlers/messages/3rdparty.go
🧰 Additional context used
🧬 Code graph analysis (8)
internal/sms-gateway/handlers/thirdparty/auth.go (5)
internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/thirdparty/permissions.go (1)
ScopeTokensManage(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/handlers/settings/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/settings/permissions.go (2)
ScopeRead(4-4)ScopeWrite(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (4)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/modules/auth/service.go (1)
Service(24-36)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (2)
SetScopes(15-17)ScopeAll(10-10)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (2)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
SetUser(88-90)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
SetScopes(15-17)
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/webhooks/permissions.go (3)
ScopeList(4-4)ScopeWrite(5-5)ScopeDelete(6-6)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
internal/sms-gateway/handlers/3rdparty.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(16-20)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
NewBasic(21-59)UserRequired(114-122)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
NewJWT(13-38)
internal/sms-gateway/handlers/logs/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/logs/permissions.go (1)
ScopeRead(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (19)
internal/sms-gateway/handlers/logs/permissions.go (1)
3-5: LGTM!Clean permission scope definition following the established pattern.
internal/sms-gateway/handlers/devices/permissions.go (1)
3-6: LGTM!Permission scopes are clearly defined and follow the established naming convention.
api/requests.http (2)
4-4: LGTM!JWT token variable added to support Bearer authentication alongside Basic auth.
38-39: Verify Bearer token authentication works with JWT service.The Authorization header now uses Bearer token instead of Basic auth. Ensure the JWT middleware and service are properly wired to handle this authentication method.
internal/sms-gateway/handlers/webhooks/permissions.go (1)
3-7: LGTM!Permission scopes are well-defined. Note that the HTTP request examples use
"webhooks:read"which doesn't matchScopeList = "webhooks:list"- this was flagged in the api/requests.http review.cmd/sms-gateway/main.go (1)
13-16: LGTM!JWTAuth security definition properly added to Swagger documentation, following the same pattern as existing security definitions.
internal/sms-gateway/handlers/thirdparty/permissions.go (1)
3-5: LGTM!Permission scope for token management is clearly defined. Note that this scope isn't included in the example token request in api/requests.http - this may be intentional for security reasons (requiring separate privileged access for token management).
internal/sms-gateway/handlers/settings/permissions.go (1)
3-6: LGTM!Permission scopes correctly defined and align with the token request examples in api/requests.http.
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (3)
13-19: LGTM - Proper Bearer token detection and fallback.The middleware correctly:
- Uses case-insensitive comparison for "Bearer " (per HTTP specs)
- Validates minimum token length (> 7 characters for "Bearer " + token)
- Falls through to next handler for non-Bearer auth, allowing other authentication methods
33-36: LGTM - Context properly populated.The middleware correctly:
- Sets the authenticated user in request context
- Applies JWT token scopes for permission checks
- Continues to the next handler
The scope propagation integrates well with the permission middleware system.
23-26: Review comment is incorrect—ParseToken already implements revocation checks.The code in
internal/sms-gateway/jwt/service.go(lines 105–111) explicitly validates tokens against the revocation list withinParseToken. The method callss.revoked.IsRevoked(ctx, claims.ID)and returnsErrTokenRevokedif the token is revoked. TheRevokedStoragedependency is properly initialized and required by the service constructor.Likely an incorrect or invalid review comment.
internal/sms-gateway/handlers/logs/3rdparty.go (1)
3-46: Logs 3rd‑party handler wiring looks consistentSwitch to
users.Userpluspermissions.RequireScope(ScopeRead)+userauth.WithUsermatches the new auth model; SwaggerJWTAuthannotation also aligns. No issues from this change.internal/sms-gateway/handlers/settings/3rdparty.go (1)
8-129: Settings 3rd‑party handlers align with new user/scope modelMigration to
users.Userpluspermissions.RequireScope(ScopeRead/ScopeWrite)anduserauth.WithUseris consistent and preserves existing business logic aroundsettingsSvc. No issues spotted.internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
1-36: Permission middleware is straightforward and consistent
SetScopes,HasScope, andRequireScopeare simple and do what the handlers expect (including honoringScopeAll). The locals key is kept internal, which is good for encapsulation.internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
8-113: Webhooks 3rd‑party handlers: user/scopes wiring looks goodThe switch to
users.Userplus per‑route scopes (ScopeList,ScopeWrite,ScopeDelete) anduserauth.WithUseris coherent, and the delete route path"/:id"matches the documented/3rdparty/v1/webhooks/{id}. Implementation looks solid.internal/sms-gateway/handlers/3rdparty.go (4)
8-8: LGTM! Import additions align with new dependencies.The new imports for JWT authentication, users service, and third-party auth handler are all utilized in the updated code.
Also applies to: 11-11, 13-14
23-25: LGTM! Service and handler fields properly typed.The new fields for users service, JWT service, and auth handler are correctly typed and well-organized.
Also applies to: 32-32
74-78: LGTM! Authentication middleware chain is correctly ordered.The middleware sequence (basic auth → JWT auth → user required) properly allows both authentication methods while enforcing that at least one succeeds.
80-80: Auth handler registration is correct.The
/authendpoints are for token management only (POST/tokento create managed tokens, DELETE/token/:jtito revoke). Both are already protected byuserauth.WithUser()middleware in their handlers, and requiring group-level authentication is the correct design. No issues detected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
internal/sms-gateway/handlers/mobile.go (1)
56-56: Handle the error fromnanoid.Standard.This issue was already flagged in a previous review. The error from
nanoid.Standard(21)is still being ignored, which can cause a nil pointer panic whenh.idGen()is called at line 131.
🧹 Nitpick comments (4)
internal/sms-gateway/handlers/thirdparty/auth.go (1)
87-89: Token revocation endpoint is stubbed.The
DELETE /token/:jtiendpoint is registered but returnsErrNotImplemented. Given this is a draft PR, this is expected.When ready to implement this handler, it should:
- Extract the
jtiparameter from the route- Call
h.jwtSvc.RevokeToken(c.Context(), jti)- Return appropriate success/error responses
Would you like me to generate the implementation or open a tracking issue?
internal/sms-gateway/users/cache.go (2)
15-15: Consider making the TTL configurable.A hardcoded 1-hour TTL may be too long in security-sensitive environments. Consider allowing this to be configured via environment variables or application config, especially since this cache is keyed by authentication credentials.
47-58: Review the sliding expiration strategy.The
cache.AndSetTTL(loginCacheTTL)call extends the cache TTL on every access, implementing a sliding expiration window. While this improves user experience by keeping frequently-accessed credentials cached, it also means:
- Compromised credentials could remain cached indefinitely if accessed regularly
- Cache entries persist longer than the initial 1-hour TTL suggests
Consider whether absolute expiration (without TTL extension on reads) would be more appropriate for security-sensitive credential caching.
Additionally, consider adding observability for cache hit/miss rates to monitor effectiveness and detect anomalies.
internal/sms-gateway/handlers/mobile.go (1)
132-133: Clarify the slicing logic that skips index 6.The generated ID is 21 characters, but
usernameusesid[:6](indices 0-5) andpasswordusesid[7:](indices 7+), deliberately skipping the character at index 6. While this might be intentional (e.g., as a separator or to avoid ambiguous characters), the logic isn't documented and could confuse future maintainers.Consider adding a comment explaining why index 6 is skipped:
+ // Generate credentials from nanoid: first 6 chars for username, chars after index 6 for password username = strings.ToUpper(id[:6]) password = strings.ToLower(id[7:])
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
api/requests.http(5 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/users/cache.go(1 hunks)pkg/cache/redis.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- internal/sms-gateway/handlers/messages/permissions.go
- pkg/cache/redis.go
- api/requests.http
🧰 Additional context used
🧬 Code graph analysis (3)
internal/sms-gateway/users/cache.go (4)
pkg/cache/typed.go (2)
Typed(14-16)NewTyped(18-22)internal/sms-gateway/users/domain.go (1)
User(5-10)pkg/cache/options.go (2)
AndSetTTL(64-68)WithTTL(22-30)pkg/cache/errors.go (2)
ErrKeyNotFound(7-7)ErrKeyExpired(9-9)
internal/sms-gateway/handlers/thirdparty/auth.go (5)
internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/thirdparty/permissions.go (1)
ScopeTokensManage(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/handlers/mobile.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/modules/auth/service.go (1)
Service(24-36)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
GetUser(102-109)NewBasic(21-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (10)
internal/sms-gateway/handlers/thirdparty/auth.go (3)
35-38: Routes properly secured with scope-based access control.Both endpoints correctly require the
tokens:managescope and user authentication via middleware. The past review concern about missing@Securityannotations in the OpenAPI documentation has been addressed (see lines 54-55).
52-85: Security annotations properly added per previous review.The OpenAPI documentation now includes
@Security ApiAuthand@Security JWTAuthannotations (lines 54-55), correctly reflecting the authentication and scope requirements enforced by the middleware. The handler implementation is straightforward and correct.
40-43: ****The code already handles zero-value TTL appropriately. When
ttlis omitted from the request,GenerateToken(line 58–60 ofinternal/sms-gateway/jwt/service.go) explicitly defaults it tos.config.TTL, which is guaranteed to be positive by configuration validation. Tokens will always receive a valid expiration time.Likely an incorrect or invalid review comment.
internal/sms-gateway/users/cache.go (3)
17-30: LGTM!The wrapper struct correctly mirrors the User domain model and implements the required cache.Item interface using standard JSON serialization.
60-72: LGTM!The Set method correctly converts the domain User to the cache wrapper and stores it with the configured TTL. Error handling is appropriate.
74-81: Cache invalidation confirmed in password change flowThe Delete method implementation is correct and properly integrated. Cache invalidation occurs at
internal/sms-gateway/users/service.go:102within the ChangePassword method, where the old password credentials are removed from cache immediately after password validation and before updating the password hash. This prevents stale login cache entries from persisting after a password change.internal/sms-gateway/handlers/mobile.go (4)
197-207: LGTM! Clean migration tousers.User.The signature change from
models.Usertousers.Useris consistent with the broader migration. The function correctly usesuser.IDwhich exists in the new type.
230-232: LGTM! Correct delegation to users service.The change properly delegates password changes to
usersSvc.ChangePasswordwith the context and user ID from the authenticated device. The updated error message is appropriately generic.Note: Line 275 has a pre-existing comment indicating this endpoint should eventually use
userauthprotection instead ofdeviceauth, but that's outside the scope of this change.
242-243: LGTM! Consistent authentication middleware migration.The routes correctly use
userauth.NewBasic(h.usersSvc)for user authentication, which is consistent with the migration from auth service to users service for user-related authentication flows.Also applies to: 260-260
127-150: The review comment's concern is incorrect and should be disregarded.The verification confirms that
users.User.IDis always equal to the login username:
- In
internal/sms-gateway/users/service.go:51, theCreatemethod explicitly setsID: username, using the provided username parameter as the user's ID.- The
Userstruct contains only anIDfield with no separate Login or Username field.- For new users, the generated 6-character string is passed as
usernametoCreate(), which becomes theuser.ID.- For existing users,
user.IDIS the login username.Both code paths return the correct login username in the response.
Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
api/requests.http (1)
4-223: Re‑verify tokenscopesagainst permission constants (especiallydevices:write).The addition of
@jwtTokenand the JWT‑secured examples for messages and devices look good, and the new/3rdparty/v1/auth/tokenand/3rdparty/v1/auth/token/{id}endpoints make sense.However, the scopes in the token request body still deserve another pass:
"scopes": [ "messages:send", "messages:read", "devices:list", "devices:write", "webhooks:list", "webhooks:write", "settings:read", "settings:write", "logs:read" ]A previous review found that the defined device permissions were
"devices:list"and"devices:delete"(not"devices:write"), and that webhooks scopes needed to be"webhooks:list"/"webhooks:write"/"webhooks:delete". You’ve already aligned the webhooks scopes here, but it’s unclear whether"devices:write"now exists as a constant or still mismatches the code.To avoid hard‑to‑debug authorization failures, please re‑check that every scope string in this sample exactly matches the corresponding permission constants (devices, webhooks, messages, settings, logs) and update either the constants or this request accordingly.
You can re‑run a quick scan like this to confirm:
#!/bin/bash # List all defined permission scopes rg -n 'Scope[A-Za-z0-9_]*\s*=\s*".*:.*"' --type go # Show where scopes are enforced in handlers/middleware rg -n 'RequireScope\(' --type go -A2
🧹 Nitpick comments (10)
internal/sms-gateway/jwt/models.go (1)
11-37: Keep GORM model and SQL migration in sync (and consider FK definition here).The
tokenModelshape and tags line up with thetokensDDL in20251121071748_add_tokens.sql, but the SQL migration also defines thefk_tokens_userforeign key while the GORM model does not. SinceMigrateusesAutoMigrateand you also ship an explicit SQL migration, you now have two schema sources of truth.To avoid future drift (e.g., if columns or indexes change) and to keep FKs consistent across environments that only run
Migrate, consider either:
- documenting that goose SQL is authoritative and
Migrateis only for tests/dev, or- adding equivalent FK metadata to the GORM model (or dropping one of the mechanisms).
internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql (1)
3-18: Consider indexingexpires_atif you’ll query/cleanup by expiry.The tokens schema looks consistent with the Go model and has good indexes on
user_idandrevoked_at. If you plan to frequently select or delete tokens based onexpires_at(e.g., cleanup of expired tokens), consider adding an index onexpires_at(or a composite like(user_id, expires_at)) to avoid future table scans.internal/sms-gateway/jwt/module.go (1)
10-30: JWT module wiring looks correct; double‑check need for the private cache provider.The FX wiring for
Service(real vsnewDisabled()depending onConfig.Secret) and the migration registration viadb.RegisterMigration(Migrate)both look good.One minor thing: the private
cache.Cacheprovider created withfactory.New("jwt")isn’t consumed by any other provider in this module as shown. If nothing injwtcurrently depends oncache.Cache, you could drop this provider to reduce noise; if you plan to use it for revocation caching or similar, leaving it is fine but maybe add a brief comment for intent.internal/sms-gateway/jwt/repository.go (1)
28-46: Token revocation semantics and timestamp portability
Revokealways returns nil even when no row matches(id,user_id,revoked_at is null), and it usesgorm.Expr("NOW()")for the timestamp. If you ever need to distinguish “no such token / belongs to another user / already revoked” from “successfully revoked”, consider checking the update’sRowsAffectedand surfacing a domain error. Also, using a Gotime.Now()value (e.g.,Update("revoked_at", time.Now())) would avoid tying this to dialects that don’t supportNOW().internal/sms-gateway/handlers/thirdparty/auth.go (3)
36-40: Middleware wiring is sound; consider renamingerrorHanlderand clarifying responsibilitiesThe route setup with
router.Use(h.errorHanlder)pluspermissions.RequireScopeanduserauth.WithUsergives a clear protection story. Two small polish points:
- The method name
errorHanlderlooks like a typo; renaming toerrorHandlerwill make usages easier to grep.- This middleware currently only maps
ErrDisabled,ErrInitFailed, andErrInvalidConfig. If other JWT‑related errors need consistent HTTP mapping (e.g. always 401 vs 500), consider documenting whether they’re expected to be handled here or by a shared/global error handler.Also applies to: 111-128
42-52: Clarify TTL behavior in docs to match implementation
tokenRequest.TTLis optional (omitempty) andGenerateTokenfalls back toconfig.TTLwhen it’s zero while also capping by the configured maximum. The Swagger description currently just says “with specified scopes and ttl”, which reads as if TTL were always required.Consider adjusting the comment above
postTokento explicitly state that:
scopesare required, andttlis optional and, when omitted or zero, defaults to the configured maximum TTL.This will make the behavior more obvious to API consumers.
Also applies to: 54-87
89-109: Confirm desired behavior for revoking non‑existent or foreign tokens
deleteTokendelegates tojwtSvc.RevokeToken, which in turn calls the repository’sRevokewithout surfacing whether any row was actually updated. As a result, a request with a non‑existentjti(or one that belongs to another user) will still return 204.If you want idempotent “best‑effort revoke” semantics, this is fine; if instead clients should get feedback (e.g. 404 when no matching token exists), you’ll need the repository to report that distinction and translate it here.
internal/sms-gateway/openapi/docs.go (1)
1016-1050: TTL /time.Durationschema may not match actual JSON shape
internal_sms-gateway_handlers_thirdparty.tokenRequest.ttlreferencestime.Duration, which this spec models as anintegerwith nanosecond‑based enum values. In Go,time.Durationis typically marshaled as a string (e.g."1h","30s") in JSON, so clients relying on the schema may send integers while the server expects duration strings.If the intended wire format is the default Go duration string, consider:
- Using a string alias type for TTL and annotating it accordingly in the handler/model, or
- Overriding the schema for
time.Durationvia swag annotations/custom type configuration,and then regenerating this file rather than editing it directly.
Also applies to: 1822-1844
internal/sms-gateway/jwt/service.go (2)
45-86: TTL validation/capping is good; watch Go version for builtinminThe validation around
userID,scopes, and non‑negative TTL plus the “0 means default config TTL” behavior all look solid, and capping expiry withmin(ttl, s.config.TTL)enforces a max TTL.The only caveat is that the generic builtin
minrequires Go 1.21+. If yourgo.modstill targets an older Go version, this won’t compile. In that case you could replace it with an explicit clamp, e.g.:- now := time.Now() - claims := &Claims{ - RegisteredClaims: jwt.RegisteredClaims{ - ID: s.idFactory(), - Issuer: s.config.Issuer, - Subject: userID, - IssuedAt: jwt.NewNumericDate(now), - ExpiresAt: jwt.NewNumericDate(now.Add(min(ttl, s.config.TTL))), - }, - UserID: userID, - Scopes: scopes, - } + now := time.Now() + effectiveTTL := ttl + if effectiveTTL > s.config.TTL { + effectiveTTL = s.config.TTL + } + claims := &Claims{ + RegisteredClaims: jwt.RegisteredClaims{ + ID: s.idFactory(), + Issuer: s.config.Issuer, + Subject: userID, + IssuedAt: jwt.NewNumericDate(now), + ExpiresAt: jwt.NewNumericDate(now.Add(effectiveTTL)), + }, + UserID: userID, + Scopes: scopes, + }
88-118: Make all token‑validation failures surface asErrInvalidTokenfor callersRight now, parse/validation failures from
jwt.ParseWithClaimsare wrapped asfmt.Errorf("failed to parse token: %w", err), while only the!ok || !parsedToken.Validbranch returnsErrInvalidToken. That means callers can’t reliably useerrors.Is(err, ErrInvalidToken)to detect “invalid/expired token” in all cases and may end up treating many invalid tokens as generic server errors.If you want a consistent contract for downstream code (e.g., jwtauth middleware mapping all invalid tokens to 401), consider mapping all token‑validation failures to
ErrInvalidToken(possibly logging the wrapped error separately) instead of returning a wrapped error here.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
api/requests.http(5 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/models.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/repository.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql(1 hunks)internal/sms-gateway/openapi/docs.go(17 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/sms-gateway/jwt/errors.go
🧰 Additional context used
🧬 Code graph analysis (5)
internal/sms-gateway/jwt/models.go (1)
internal/sms-gateway/models/models.go (1)
TimedModel(7-10)
internal/sms-gateway/jwt/module.go (8)
internal/sms-gateway/app.go (1)
Module(35-60)internal/config/module.go (1)
Module(23-135)internal/sms-gateway/cache/factory.go (1)
Factory(16-18)internal/sms-gateway/jwt/service.go (1)
New(22-43)internal/sms-gateway/jwt/repository.go (2)
NewRepository(14-18)Repository(10-12)internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)internal/sms-gateway/jwt/models.go (1)
Migrate(32-37)
internal/sms-gateway/handlers/thirdparty/auth.go (6)
internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/thirdparty/permissions.go (1)
ScopeTokensManage(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/jwt/errors.go (3)
ErrDisabled(6-6)ErrInitFailed(7-7)ErrInvalidConfig(8-8)
internal/sms-gateway/jwt/disabled.go (2)
internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (1)
ErrDisabled(6-6)
internal/sms-gateway/jwt/service.go (4)
internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/repository.go (1)
Repository(10-12)internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (4)
ErrInitFailed(7-7)ErrInvalidConfig(8-8)ErrInvalidToken(9-9)ErrTokenRevoked(10-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (2)
internal/sms-gateway/jwt/disabled.go (1)
8-27: Disabled service implementation looks solid.The disabled implementation cleanly satisfies
Serviceand consistently returnsErrDisabledfrom all methods, giving a predictable behavior when JWT is turned off. No state or concurrency concerns here.internal/sms-gateway/jwt/jwt.go (1)
10-27: JWT service interface and types look well‑shapedThe Service contract plus Claims/TokenInfo structs are coherent with the implementation in
service.goand the third‑party handlers; fields and JSON tags give consumers what they need without overexposing internals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
♻️ Duplicate comments (1)
api/requests.http (1)
201-219: Verify scope name "devices:write" matches defined permission constants.The past review flagged that
"devices:write"(line 212) does not match the permission constants defined indevices/permissions.go. According to the previous review, the available device permissions are"devices:list"and"devices:delete".Run the following script to confirm the available device permission scopes:
#!/bin/bash # Find all device permission scope constants rg -nP 'Scope.*=.*\"devices:' --type goUpdate line 212 to use a valid device permission constant.
🧹 Nitpick comments (4)
deployments/grafana/dashboards/jwt.json (1)
38-38: Consider using a variable for the datasource UID.The datasource UID
"edqp0a73uh2bka"is hardcoded throughout the dashboard. If the datasource changes in different environments, this will require manual updates.Consider using
${DS_PROMETHEUS}or a similar variable instead of hardcoding the UID, or ensure the UID is documented in deployment instructions.Also applies to: 85-85, 149-149
internal/sms-gateway/jwt/service.go (2)
78-88: Consider removing the redundant UserID field from Claims.The
Claimsstruct storesUserIDseparately (line 86) even thoughRegisteredClaims.Subjectalready holds the same value (line 82). This duplication may cause maintenance issues if they ever diverge.If the custom
UserIDfield serves no distinct purpose, consider removing it from theClaimsstruct injwt.goand using onlySubjectthroughout the codebase.
63-66: Consider validating that scopes are from an allowed set.The function requires
len(scopes) > 0but does not validate that the provided scopes are valid permission constants. Invalid scopes will be accepted and stored, potentially causing authorization failures later.Consider adding a scope validation step that checks each scope against the defined permission constants (e.g.,
messages:send,devices:list, etc.) to catch configuration errors early.internal/sms-gateway/jwt/metrics.go (1)
99-103: Consider adding panic recovery in duration observation helpers.If the callback
f()panics,timer.ObserveDuration()is never called, which could skew latency metrics. This applies to all three Observe* methods.Apply this pattern to all three observation methods:
func (m *Metrics) ObserveIssuance(f func()) { timer := prometheus.NewTimer(m.issuanceDurationHistogram) + defer timer.ObserveDuration() f() - timer.ObserveDuration() }Using
deferensures the duration is recorded even iff()panics.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
api/requests.http(5 hunks)deployments/grafana/dashboards/jwt.json(1 hunks)deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/metrics.go(1 hunks)internal/sms-gateway/jwt/models.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql
🚧 Files skipped from review as they are similar to previous changes (4)
- internal/sms-gateway/jwt/module.go
- internal/sms-gateway/handlers/thirdparty/auth.go
- internal/sms-gateway/jwt/errors.go
- internal/sms-gateway/jwt/models.go
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/jwt/service.go (5)
internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/repository.go (1)
Repository(10-12)internal/sms-gateway/jwt/metrics.go (3)
Metrics(24-31)StatusError(20-20)StatusSuccess(19-19)internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (4)
ErrInitFailed(7-7)ErrInvalidParams(9-9)ErrInvalidToken(10-10)ErrTokenRevoked(11-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (2)
internal/sms-gateway/jwt/service.go (1)
58-61: Good fix—userID validation now prevents empty Subject claims.The validation added here addresses the past review concern about empty "sub" claims. This ensures JWTs are spec-compliant.
internal/sms-gateway/jwt/metrics.go (1)
23-31: LGTM—Metrics structure is well-organized.The metrics cover all three core JWT operations (issuance, validation, revocation) with both counters and duration histograms. The use of a single
statuslabel keeps cardinality low.Note: The absence of a
reasonlabel for granular error classification is consistent with the simplified error tracking approach, though it limits observability of specific failure modes (expired vs. revoked vs. malformed tokens).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
deployments/prometheus/alerts/jwt-alerts.yml (2)
24-32: Critical: Label mismatch persists—metric does not expose a "reason" label.This issue was previously identified but remains unresolved. The alert queries
reason="database_error", buttokensRevokedCounterininternal/sms-gateway/jwt/metrics.go(line 50-55) only exposes astatuslabel. The alert will never fire.
34-42: Critical: Label mismatch persists, and inconsistent division protection.Two issues:
Label mismatch (same as previously identified): The alert queries
reason=~"expired|revoked", buttokensValidatedCounterininternal/sms-gateway/jwt/metrics.go(lines 43-48) only exposes astatuslabel. This alert will never fire.Inconsistent division protection: This alert uses
max(..., 0.00001)while line 5 usesclamp_min(..., 1e-9). Both work, but the inconsistency makes the codebase harder to maintain.Consider standardizing on
clamp_minfor consistency:- expr: rate(sms_auth_jwt_tokens_validated_total{status="error",reason=~"expired|revoked"}[5m]) / max(rate(sms_auth_jwt_tokens_validated_total[5m]), 0.00001) > 0.1 + expr: rate(sms_auth_jwt_tokens_validated_total{status="error"}[5m]) / clamp_min(rate(sms_auth_jwt_tokens_validated_total[5m]), 1e-9) > 0.1Note: This also removes the non-existent
reasonlabel filter. If you need per-reason alerting, add thereasonlabel to the metric inmetrics.gofirst.
🧹 Nitpick comments (1)
internal/sms-gateway/jwt/metrics.go (1)
57-79: Consider extracting shared histogram buckets constant.All three histograms use identical bucket definitions. For maintainability, consider extracting to a shared constant:
+var jwtDurationBuckets = []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1} + func NewMetrics() *Metrics { return &Metrics{ ... issuanceDurationHistogram: promauto.NewHistogram(prometheus.HistogramOpts{ Namespace: "sms", Subsystem: "auth", Name: MetricIssuanceDurationSeconds, Help: "JWT issuance duration in seconds", - Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1}, + Buckets: jwtDurationBuckets, }),This makes it easier to adjust bucket ranges consistently across all JWT operations.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
deployments/grafana/dashboards/jwt.json(1 hunks)deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)internal/sms-gateway/jwt/metrics.go(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (17)
deployments/prometheus/alerts/jwt-alerts.yml (2)
4-12: LGTM! Division-by-zero protection correctly implemented.The alert correctly uses
clamp_minto prevent division by zero and queries thestatuslabel that exists in the metrics definition.
14-22: LGTM! Latency alert correctly configured.The histogram quantile query correctly references the issuance duration metric, and the 0.5s threshold is appropriate given the histogram buckets defined in
metrics.go.deployments/grafana/dashboards/jwt.json (12)
82-92: LGTM! Panel correctly queries the tokens issued metric.The query correctly references
sms_auth_jwt_tokens_issued_total, which matches the metric defined ininternal/sms-gateway/jwt/metrics.go.
146-156: LGTM! Validation success rate correctly calculated.The panel correctly uses
sms_auth_jwt_tokens_validated_totalwith proper division-by-zero protection viaclamp_min.
208-218: LGTM! Revocation rate panel correctly configured.The query correctly references
sms_auth_jwt_tokens_revoked_total.
308-318: LGTM! Issuance rate timeseries correctly configured.The query correctly aggregates by
status, which will display separate series for success and error rates.
383-396: LGTM! Panel correctly uses the existing metric with status filter.The query correctly filters
sms_auth_jwt_tokens_issued_totalbystatus="error", which is the proper approach given the metric definition inmetrics.go. The past concern about a non-existent metric has been resolved.
483-513: LGTM! Latency distribution panel correctly configured.The histogram quantile queries correctly reference
sms_auth_jwt_issuance_duration_seconds_bucketfor p99, p95, and p50 percentiles.
603-613: LGTM! Validation success rate timeseries correctly configured.The query correctly uses
sms_auth_jwt_tokens_validated_totalwith proper division protection.
667-681: LGTM! Panel correctly uses the existing metric aggregated by status.The query correctly aggregates
sms_auth_jwt_tokens_validated_totalbystatus, which will show the breakdown in a pie chart. The past concern about a non-existent metric has been resolved.
770-800: LGTM! Validation latency panel correctly configured.The histogram quantile queries correctly reference
sms_auth_jwt_validation_duration_seconds_bucket.
890-900: LGTM! Revocation rate timeseries correctly configured.The query correctly aggregates by
statusto show success and error rates separately.
990-1000: LGTM! Panel correctly uses the existing metric with status filter.The query correctly filters
sms_auth_jwt_tokens_revoked_totalbystatus="error"and includes proper division protection withclamp_min. The past concern about a non-existent metric has been resolved.
1090-1120: LGTM! Revocation latency panel correctly configured.The histogram quantile queries correctly reference
sms_auth_jwt_revocation_duration_seconds_bucket.internal/sms-gateway/jwt/metrics.go (3)
23-31: LGTM! Metrics struct is well-designed.The struct appropriately uses
CounterVecfor labeled counters andHistogramfor duration tracking. Field names are clear and follow Go conventions.
83-96: LGTM! Increment methods correctly implemented.The increment methods properly delegate to the underlying counter's
WithLabelValuesandIncmethods.
98-117: LGTM! Observation methods correctly use timer pattern.The observe methods correctly use
prometheus.NewTimerwith deferredObserveDurationto measure operation duration, which ensures timing is captured even if the operation panics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (1)
deployments/prometheus/alerts/jwt-alerts.yml (1)
5-5: PromQL expressions look correct with proper safeguards.The past division-by-zero and label-mismatch issues have been addressed: expressions now use
clamp_min()/max()to guard against zero denominators, and label selectors use the correctstatuslabel that exists in the metrics.Also applies to: 15-15, 25-25, 35-35
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
deployments/prometheus/alerts/jwt-alerts.yml (1)
5-5: Harmonize zero-protection thresholds for consistency.Alert 1 uses
clamp_min(..., 1e-9)while Alert 4 usesmax(..., 0.00001). Both prevent division-by-zero, but the minimum thresholds differ by several orders of magnitude. For consistency across alerts with similar semantics, consider using the same minimum value.Apply this diff to align both to the same minimum:
- expr: rate(sms_auth_jwt_tokens_validated_total{status="error"}[5m]) / clamp_min(rate(sms_auth_jwt_tokens_validated_total[5m]), 1e-9) > 0.05 + expr: rate(sms_auth_jwt_tokens_validated_total{status="error"}[5m]) / clamp_min(rate(sms_auth_jwt_tokens_validated_total[5m]), 0.00001) > 0.05Or update Alert 4 to match Alert 1:
- expr: rate(sms_auth_jwt_tokens_validated_total{status="error"}[5m]) / max(rate(sms_auth_jwt_tokens_validated_total[5m]), 0.00001) > 0.1 + expr: rate(sms_auth_jwt_tokens_validated_total{status="error"}[5m]) / clamp_min(rate(sms_auth_jwt_tokens_validated_total[5m]), 0.00001) > 0.1Also applies to: 35-35
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (2)
deployments/prometheus/alerts/jwt-alerts.yml (2)
1-42: Excellent work addressing previous critical issues.All four alerts now correctly use the
status="error"label (fixed from non-existentreasonlabel references in earlier commits) and both division operations are protected against zero denominators viaclamp_min()andmax().
24-32: Verify Alert 3 sensitivity for production operations.The
JWT_Revocation_Failuresalert fires onrate(...) > 0—any revocation error within a 5m window with critical severity. Over 5 minutes, even a single error will trigger. Depending on your operational baseline, this may cause alert fatigue or may be intentional given the security criticality of revocation.Confirm this threshold aligns with expected error rates and operational oncall procedures.
c533392 to
a66a972
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
pkg/cache/memory.go (1)
177-241: Fix data race onmemoryItem.validUntilwhen updating TTL inGetIn the
Getpath with options,item.validUntilis mutated underm.mux.Lock, butgetItemlater callsitem.isExpired(time.Time)without holding any lock. Other goroutines can concurrently callGetwith TTL‑updating options on the same key, leading to unsynchronized reads/writes ofvalidUntiland a data race under the Go memory model (and likely flagged by the race detector).A minimal fix is to avoid mutating
memoryItemin place and instead create a newmemoryItemwith the updated TTL and swap it into the map while holding the write lock, so the structs themselves become immutable after creation. That way, readers may see either the old or new item pointer, but no field is ever written concurrently with being read.One way to adjust
Getis:func (m *memoryCache) Get(_ context.Context, key string, opts ...GetOption) ([]byte, error) { return m.getValue(func() (*memoryItem, bool) { if len(opts) == 0 { m.mux.RLock() item, ok := m.items[key] m.mux.RUnlock() return item, ok } o := getOptions{} o.apply(opts...) - m.mux.Lock() - item, ok := m.items[key] - if !ok { - // item not found, nothing to do - } else if o.delete { - delete(m.items, key) - } else if !item.isExpired(time.Now()) { - if o.validUntil != nil { - item.validUntil = *o.validUntil - } else if o.setTTL != nil { - item.validUntil = time.Now().Add(*o.setTTL) - } else if o.updateTTL != nil { - item.validUntil = item.validUntil.Add(*o.updateTTL) - } else if o.defaultTTL { - item.validUntil = time.Now().Add(m.ttl) - } - } - m.mux.Unlock() - - return item, ok + m.mux.Lock() + defer m.mux.Unlock() + + item, ok := m.items[key] + if !ok { + return nil, false + } + + if o.delete { + delete(m.items, key) + return item, true + } + + now := time.Now() + if item.isExpired(now) { + // Let getItem report the expired state; cleanup() will remove it later. + return item, true + } + + // Avoid mutating shared state in-place; create a new item with updated TTL. + updated := &memoryItem{ + value: item.value, + validUntil: item.validUntil, + } + + if o.validUntil != nil { + updated.validUntil = *o.validUntil + } else if o.setTTL != nil { + updated.validUntil = now.Add(*o.setTTL) + } else if o.updateTTL != nil { + updated.validUntil = updated.validUntil.Add(*o.updateTTL) + } else if o.defaultTTL { + updated.validUntil = now.Add(m.ttl) + } + + m.items[key] = updated + return updated, true }) }This preserves the existing API and semantics while removing the shared‑state mutation that causes the race.
Also applies to: 366-400
pkg/cache/options.go (1)
25-48:WithTTLimplementation contradicts its own documentationDocs say “A TTL of zero or negative means the item will not expire”, but the implementation always sets
validUntil = time.Now().Add(ttl). Forttl <= 0this yields an immediate or past expiration instead of “no expiry”.You probably want to return early when
ttl <= 0:func WithTTL(ttl time.Duration) Option { return func(o *options) { - if ttl <= 0 { - o.validUntil = time.Time{} - } - - o.validUntil = time.Now().Add(ttl) + if ttl <= 0 { + // Zero value means "no expiration" + o.validUntil = time.Time{} + return + } + + o.validUntil = time.Now().Add(ttl) } }internal/sms-gateway/handlers/mobile.go (1)
197-233: Change‑password handler conflates auth failures with server errorsSwitching
getUserCodeto useusers.Useris aligned with the new users module anduserauth.WithUser, that looks fine.In
changePassword, every error fromusersSvc.ChangePasswordis logged and then returned as401 Unauthorized:if err := h.usersSvc.ChangePassword(...); err != nil { h.Logger.Error("failed to change password", zap.Error(err)) return fiber.NewError(fiber.StatusUnauthorized, "failed to change password") }This lumps together invalid current password, missing user, cache issues, and database failures as “unauthorized”. It would be more accurate (and easier to debug) to distinguish credential errors from internal ones (e.g., map a known “login failed”/“bad credentials” sentinel to 401, and everything else to 500 while keeping the generic message).
♻️ Duplicate comments (5)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
130-133: MakeWithUserpanic message explicit instead of a nil‑pointer deref
WithUserstill doesreturn handler(*GetUser(c), c), so if it’s ever used without a preceding auth middleware that callsSetUser, it will panic with a generic nil‑pointer error. Since the comment already promises a panic, you can make the failure mode clearer:func WithUser(handler func(users.User, *fiber.Ctx) error) fiber.Handler { return func(c *fiber.Ctx) error { - return handler(*GetUser(c), c) + user := GetUser(c) + if user == nil { + panic("WithUser called without authenticated user in context") + } + return handler(*user, c) } }This keeps the contract (panic on missing user) but makes debugging mis‑wired routes much easier.
internal/sms-gateway/users/cache.go (1)
42-45: Reconsider credential-based cache keys for login cache
makeKeyhashesusernameandpassworddirectly into the cache key. This keeps deterministic, unsalted credential material in the cache layer and makes the cache usable as an authentication oracle. It also complicates safe invalidation on password changes (especially admin resets where the old password is unknown), potentially allowing old passwords to remain valid until TTL expiry.Consider instead:
- Keying by a stable, non-secret identifier (e.g., user ID) and caching whatever you actually want to reuse (e.g., user record, precomputed scopes), or
- If you must depend on credentials, deriving keys with an HMAC that includes a server-side secret and a per-user salt, and wiring explicit invalidation on password changes.
At minimum, document how password-change flows guarantee invalidation of old-credential entries.
internal/sms-gateway/handlers/mobile.go (1)
31-74: Still ignoring nanoid initialization error; can panic if idGen is nil
nanoid.Standard(21)’s error is ignored andidGenis used later (e.g., inpostDevice). If initialization fails and returns a nil function, callingh.idGen()will panic.Handle the error and fail fast (or propagate it) instead of constructing a handler with a potentially nil generator, e.g.:
- idGen, _ := nanoid.Standard(21) + idGen, err := nanoid.Standard(21) + if err != nil { + panic(fmt.Sprintf("failed to initialize ID generator: %v", err)) + }(or return the error from the constructor, depending on your DI pattern).
internal/sms-gateway/jwt/config.go (1)
12-16: Consider validatingIssuer(or documenting that it is optional)
Config.Validatedoesn’t currently enforce thatIssueris non‑empty, even though it’s written into tokens and is later required inParseTokenviajwt.WithIssuer. If your design expects a meaningful issuer, consider adding a similar non‑empty check here; otherwise, documenting that an empty issuer is explicitly supported would avoid ambiguity.Also applies to: 18-31
internal/sms-gateway/users/repository.go (1)
54-62: Still need to handleRowsAffected == 0inUpdatePassword
UpdatePasswordwraps onlyresult.Errorand treats “no rows updated” as success, so a non‑existent user ID silently passes, unlikeGetByIDwhich returnsErrNotFound. A past review already suggested checkingRowsAffected; that feedback still applies here.You can adopt the earlier diff to make the behavior consistent:
func (r *repository) UpdatePassword(ID string, passwordHash string) error { - if err := r.db.Model((*userModel)(nil)). - Where("id = ?", ID). - Update("password_hash", passwordHash).Error; err != nil { - return fmt.Errorf("can't update password: %w", err) - } + result := r.db.Model((*userModel)(nil)). + Where("id = ?", ID). + Update("password_hash", passwordHash) + + if err := result.Error; err != nil { + return fmt.Errorf("can't update password: %w", err) + } + if result.RowsAffected == 0 { + return ErrNotFound + } return nil }Please confirm this matches your expectations for GORM’s
Updatesemantics in your current version (specifically thatRowsAffected == 0indicates “no matching rows” and not a soft‑error case):GORM Update method: meaning of RowsAffected and handling non-existent rows
🧹 Nitpick comments (17)
pkg/cache/redis.go (1)
423-446: Consider atomic SetOrFail with TTL.The current implementation has a race condition: HSetNX (line 424) and HExpireAt (line 440) are separate operations. If HSetNX succeeds but HExpireAt fails, the key will be set without TTL, potentially never expiring.
While the error is returned to the caller, the key remains in Redis without expiration, which could lead to memory issues over time.
Consider implementing this atomically using a Lua script:
local key = KEYS[1] local field = ARGV[1] local value = ARGV[2] local ttl = tonumber(ARGV[3]) or 0 local exists = redis.call('HEXISTS', key, field) if exists == 1 then return {err = 'key exists'} end redis.call('HSET', key, field, value) if ttl > 0 then redis.call('HEXPIREAT', key, ttl, 'FIELDS', '1', field) end return {ok = 'OK'}Then update the Go code:
func (r *redisCache) SetOrFail(ctx context.Context, key string, value []byte, opts ...Option) error { + options := new(options) + if r.ttl > 0 { + options.validUntil = time.Now().Add(r.ttl) + } + options.apply(opts...) + + var ttl int64 + if !options.validUntil.IsZero() { + ttl = options.validUntil.Unix() + } + + result, err := r.client.Eval(ctx, setOrFailScript, []string{r.key}, key, value, ttl).Result() - val, err := r.client.HSetNX(ctx, r.key, key, value).Result() if err != nil { + if strings.Contains(err.Error(), "key exists") { + return ErrKeyExists + } return fmt.Errorf("can't set cache item: %w", err) } - - if !val { - return ErrKeyExists - } - - options := new(options) - if r.ttl > 0 { - options.validUntil = time.Now().Add(r.ttl) - } - options.apply(opts...) - - if !options.validUntil.IsZero() { - if err := r.client.HExpireAt(ctx, r.key, options.validUntil, key).Err(); err != nil { - return fmt.Errorf("can't set cache item ttl: %w", err) - } - } - return nil }internal/config/types.go (1)
29-48: Clarify semantics for empty / missing duration strings (optional)
UnmarshalTextcurrently feeds the raw string intotime.ParseDuration, which will fail on an empty string. If you expect configs likettl: ""orttl: null(or want “missing or empty” to mean “0 / disabled”), you may want to treat empty input specially, e.g. map it to0instead of returning an error. If you want it to be strictly required, this behavior is fine but should be documented in the surrounding config docs.internal/sms-gateway/jwt/disabled.go (1)
1-28: Disabled JWT service behavior is clear and consistentReturning
ErrDisabledfrom allServicemethods is a clean way to represent a globally disabled JWT subsystem, andnewDisabledhiding the concrete type behind theServiceinterface keeps the wiring simple. If you want an extra guard, you could add:var _ Service = (*disabled)(nil)but the current code is functionally fine as‑is.
internal/sms-gateway/users/domain.go (1)
5-19: User domain model and mapper are straightforwardThe
Userstruct is minimal and thenewUserhelper cleanly maps fromuserModelto the domain type. One thing to decide explicitly: ifmodelshould never benil, it may be worth documenting that contract; if it’s possible for repository code to passnil, adding a quick guard (or returningnil/error higher up) would prevent a panic.internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
66-83: Consider assigning scopes for Code‑based auth as well
NewCodenow correctly usesAuthorizeUserByCodeandSetUser, but it doesn’t set any scopes. Any routes guarded only bypermissions.RequireScope(...)(like the updated 3rd‑party devices handlers) will deny requests authenticated via the"Code …"header unless some other middleware also sets scopes.If Code‑based auth is meant to have the same effective permissions as Basic/JWT on those endpoints, consider mirroring the Basic behavior:
func NewCode(authSvc *auth.Service) fiber.Handler { @@ - user, err := authSvc.AuthorizeUserByCode(code) + user, err := authSvc.AuthorizeUserByCode(code) if err != nil { return fiber.ErrUnauthorized } - SetUser(c, *user) + SetUser(c, *user) + // Give code-based auth full access, like Basic. + permissions.SetScopes(c, []string{permissions.ScopeAll})If instead Code auth is intentionally restricted from scope‑protected endpoints, it would help to document that explicitly.
internal/sms-gateway/handlers/logs/3rdparty.go (1)
25-46: JWT security + scope middleware look good; consider documenting scope and unused paramThe added
@Security JWTAuthandpermissions.RequireScope(ScopeRead)plususerauth.WithUserare consistent and tighten access control without changing the 501 behavior. Theuser users.Userargument ingetis currently unused; if that’s intentional for future use, consider naming it_ users.Userto keep linters quiet. Also, if your OpenAPI tooling supports it, documenting that this route requires theScopeReadscope (not just the JWT scheme) would make the spec match the runtime behavior more closely.internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
9-36: Scope middleware is clear and correct; consider adding observability later
SetScopes,HasScope, andRequireScopecorrectly wire scopes via Fiber locals, andScopeAllbehaves as expected viaslices.ContainsFunc. For typical small scope lists this is perfectly fine. Longer‑term, you might want to add logging/metrics on forbidden responses inRequireScope(or at a higher layer) to aid debugging permission issues.internal/sms-gateway/handlers/thirdparty/auth.go (1)
89-131: Align errorHandler status codes with documented responses
errorHandlercan return501 Not Implementedforjwt.ErrDisabled, and potentially400 Bad Requestforjwt.ErrInvalidParams. The swagger for both/3rdparty/v1/auth/tokenand/3rdparty/v1/auth/token/{jti}doesn’t currently list 501 (and DELETE doesn’t list 400), so generated OpenAPI will under‑document possible outcomes.If you rely on these statuses, consider adding the corresponding
@Failure 400/@Failure 501entries to the comments so API consumers see the complete set of responses.internal/sms-gateway/handlers/mobile.go (1)
241-277: Basic auth now correctly wired through usersSvc; consider revisiting password route TODOUsing
userauth.NewBasic(h.usersSvc)for/mobile/v1/deviceand/mobile/v1/user/codealigns these endpoints with the new users service and theusers.Userdomain type, which looks good.There’s still a TODO noting that
/mobile/v1/user/password“Should be underuserauth.NewBasicprotection instead ofdeviceauth”. Now that usersSvc is available here, it may be a good time in a follow‑up to revisit that route’s protection model if you intend password changes to require user credentials rather than device identity.internal/sms-gateway/users/service.go (1)
71-112: Login/password caching and Delete semantics deserve a closer lookThe
Loginflow (cache → DB → cache.Set) is sensible for performance, but two aspects are worth tightening:
- You’re passing the raw
passwordinto theloginCacheAPI (Get,Set,Delete). That’s fine ifloginCacheonly uses it to derive an in‑memory key and never logs or persists the raw value, but it’s unusual enough that it’s worth double‑checking the implementation to ensure passwords aren’t stored or emitted anywhere.- In
ChangePassword, a failure fromcache.Delete(ctx, username, currentPassword)aborts the password change, even though the cache is only an optimization. It may be more robust to treatcache.ErrKeyNotFound(and possibly other transient cache errors) as non‑fatal: log a warning but still proceed to update the stored password, mirroring the tolerant behavior inLogin.Adjusting these would improve both security posture and resilience around the cache layer.
internal/sms-gateway/openapi/docs.go (2)
129-137: Clarify intended auth semantics for combinedApiAuthandJWTAuthIn Swagger 2.0, a
securityarray like:"security": [ { "ApiAuth": [] }, { "JWTAuth": [] } ]means either ApiAuth or JWTAuth is sufficient (logical OR of the objects), not that both are required. Please verify that the runtime middleware for these endpoints also allows either mechanism; if the intent is to require both, the OpenAPI should instead use a single object with both schemes.
Also applies to: 180-188, 264-273, 330-339, 421-429, 509-515, 569-577, 625-633, 665-672, 724-731, 785-792, 827-833, 887-895
1016-1050: TTL /time.Durationrepresentation may be confusing to API consumers
internal_sms-gateway_handlers_thirdparty.tokenRequest.ttlis modeled astime.Durationwith an underlying integer and enum values like nanosecond, second, minute, hour. For external clients this can be non‑obvious (nanoseconds vs seconds, human durations, etc.). Consider adjusting the handler type or swagger annotations so the docs clearly state the accepted format and units forttl(e.g., seconds as integer, or Go‑style duration strings), then regenerate this file.Also applies to: 1822-1844
internal/sms-gateway/jwt/service.go (1)
77-85: Ensure Go toolchain version supportsminontime.DurationThis code relies on
min(ttl, s.config.TTL)overtime.Duration. If you are using the Go 1.21+ builtinmin, confirm thatgo.modand CI are set to a compatible Go version; otherwise, provide a local helperfunc minDuration(a, b time.Duration) time.Durationinstead.internal/sms-gateway/modules/auth/service.go (2)
62-88:GenerateUserCodeloop depends on Go’s integerrangeand could use a clearer retry patternThe
for range 3construct is valid only on newer Go versions that support ranging over integers. Please ensure yourgo.modand CI toolchain target a compatible Go release; otherwise, rewrite as an explicitfor i := 0; i < 3; i++ { ... }loop. The retry logic itself (up to 3 attempts onrand.Read/SetOrFail) looks fine.
90-97: User lookup viaAuthorizeUserByCoderelies on usernames; consider clarifying naming
AuthorizeUserByCodepulls a string fromcodesCacheand passes it tousersSvc.GetByUsername, whileGenerateUserCode’s parameter is nameduserID. If the stored value is actually the username (which seems likely), renaming the parameter and local variable tousernamewould better reflect the behavior and avoid confusion for future maintainers.Also applies to: 132-145
pkg/cache/typed.go (1)
89-122: Clarify and surface the pointer-type requirement forTThe
newItem[T]helper correctly enforces thatTis a pointer type and fixes the earlier nil‑pointerUnmarshalissue, but that requirement is only enforced at runtime and only on read paths (Get,GetAndDelete,Drain). A misconfiguredNewTyped[SomeStruct]will happily compile and evenSet, but will start failing with"cache: type ... must be a pointer"once something callsGet.To make this easier to discover for callers, consider either:
- Explicitly documenting in the exported comments for
Typed/NewTypedthatTmust be a pointer type implementingItem(e.g.NewTyped[*User], notNewTyped[User]), or- Adding a cheap validation in
NewTyped(e.g. callingnewItem[T]()once and panicking/logging on error) so misconfiguration is caught at construction time rather than on first cache read.This doesn’t require changing the current behavior, but would make the API safer to use for new call sites.
Also applies to: 361-387
internal/sms-gateway/handlers/3rdparty.go (1)
23-33: Verify auth middleware interplay and/authroute protectionThe new wiring makes all
/3rdparty/v1routes (including/auth) go through:router.Use( userauth.NewBasic(h.usersSvc), jwtauth.NewJWT(h.jwtSvc, h.usersSvc), userauth.UserRequired(), )This is a reasonable composition (Basic OR JWT must set a user, then
UserRequiredenforces it), but it relies on bothuserauth.NewBasicandjwtauth.NewJWTbehaving as no‑ops when their respective credentials are absent (e.g. noAuthorizationheader for JWT, no Basic header for non‑login calls). If either middleware actively returns 401 on “no credentials”, it could unintentionally block the other mechanism.In particular:
/3rdparty/v1/auth/...endpoints are now behind this chain as well. If the intention is “obtain tokens via Basic, use them via JWT”, this is fine as long as missing JWT on those endpoints doesn’t cause failures while Basic succeeds.- For purely JWT‑protected endpoints, missing Basic headers must not cause errors from
NewBasic.It’d be good to confirm these contracts in the respective middlewares (and ideally with tests) so that adding JWT auth here doesn’t regress existing Basic‑auth flows.
If you’d like, I can sketch table‑driven tests that assert the desired behavior for combinations of
{Basic, JWT}presence on representative endpoints.Also applies to: 35-67, 69-81
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (62)
api/mobile.http(1 hunks)api/requests.http(5 hunks)cmd/sms-gateway/main.go(1 hunks)configs/config.example.yml(1 hunks)deployments/grafana/dashboards/jwt.json(1 hunks)deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)go.mod(1 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(6 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(3 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(10 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(8 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(8 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/jwt/config.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/metrics.go(1 hunks)internal/sms-gateway/jwt/models.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/repository.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/openapi/docs.go(17 hunks)internal/sms-gateway/users/cache.go(1 hunks)internal/sms-gateway/users/domain.go(1 hunks)internal/sms-gateway/users/errors.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)internal/sms-gateway/users/service.go(1 hunks)pkg/cache/cache.go(1 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(13 hunks)pkg/cache/options.go(5 hunks)pkg/cache/redis.go(10 hunks)pkg/cache/typed.go(1 hunks)
💤 Files with no reviewable changes (3)
- internal/sms-gateway/models/models.go
- internal/sms-gateway/modules/auth/module.go
- internal/sms-gateway/modules/auth/repository.go
✅ Files skipped from review due to trivial changes (2)
- pkg/cache/errors.go
- pkg/cache/cache.go
🚧 Files skipped from review as they are similar to previous changes (25)
- internal/config/module.go
- internal/sms-gateway/users/models.go
- internal/config/config.go
- configs/config.example.yml
- internal/sms-gateway/handlers/thirdparty/permissions.go
- internal/sms-gateway/jwt/models.go
- internal/sms-gateway/users/errors.go
- deployments/prometheus/alerts/jwt-alerts.yml
- internal/sms-gateway/jwt/module.go
- api/mobile.http
- internal/sms-gateway/handlers/settings/permissions.go
- internal/sms-gateway/handlers/messages/3rdparty.go
- internal/sms-gateway/handlers/devices/permissions.go
- internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go
- internal/sms-gateway/modules/settings/models.go
- internal/sms-gateway/handlers/messages/permissions.go
- internal/sms-gateway/handlers/logs/permissions.go
- internal/sms-gateway/handlers/webhooks/3rdparty.go
- internal/sms-gateway/handlers/settings/3rdparty.go
- cmd/sms-gateway/main.go
- api/requests.http
- internal/sms-gateway/users/module.go
- internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql
- go.mod
- deployments/grafana/dashboards/jwt.json
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/handlers/thirdparty/module.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/memory.gopkg/cache/redis.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/memory.gopkg/cache/redis.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
🔇 Additional comments (29)
internal/sms-gateway/handlers/webhooks/permissions.go (1)
3-7: LGTM! Clean permission scope definitions.The permission constants follow a clear
resource:actionpattern and Go naming conventions. The three scopes (list, write, delete) provide a sensible access control model for webhook operations.One optional consideration: verify that
ScopeWriteappropriately covers both create and update operations in your API design. If you need granular control between creating new webhooks versus updating existing ones, you might consider separate scopes in the future.internal/sms-gateway/models/migration.go (2)
9-10: Unused embedded filesystem.The
migrationsvariable is declared but never used in this file. If it's not used elsewhere in the package, consider removing it.Run the following script to check if
migrationsis referenced elsewhere:
13-13: Verify User migration is handled elsewhere and check Device dependencies.The removal of User from the migration aligns with the broader refactor, but please confirm:
- User migration is properly handled in the new jwt or users module
- The Device model doesn't have foreign key dependencies on User that could break
Run the following script to verify:
pkg/cache/redis.go (1)
40-44: Previous critical issue resolved.All Lua script Redis commands (HExpireAt, HTTL, HExpire) now correctly use the Redis 7.4.0+ format with
'FIELDS', '1', fieldparameters. The migration is complete and consistent.internal/config/types.go (1)
11-27: Duration wrapper and nil‑safe accessors look goodThe
Durationalias and itsDuration()/String()methods are straightforward, handlenilsafely, and are appropriate for config use as a thin wrapper overtime.Duration. No issues from a correctness or API‑shape standpoint.pkg/cache/memory.go (2)
9-28: Overall memory cache structure and TTL model look solidThe
memoryCache/memoryItemdesign, default TTL handling inNewMemoryandnewItem, and theisExpiredhelper form a clear, easy‑to‑reason‑about in‑memory implementation. Zero TTL meaning “no expiration by default” is consistent with the docs, andErrKeyNotFoundvsErrKeyExpiredseparation viagetItem/getValueis clean.Also applies to: 58-92, 331-353
93-176: Cleanup/Drain semantics correctly preserve non‑expired items only
Cleanupremoves expired entries first and only then invokes the callback, andDrainusescleanupto snapshotm.itemsand swap in a fresh map before copying out values. This matches the prior contract that:
- expired items are dropped before
Drainruns, andDrainreturns only non‑expired items and leaves the cache empty afterward.This keeps the behavior aligned with the existing Redis cache behavior and previous
memoryCache.cleanupsemantics. Based on learnings.Also applies to: 401-421
internal/sms-gateway/handlers/thirdparty/module.go (1)
3-16: FX module wiring and named logger usage look correct
Module()follows the established pattern: anfx.Modulewith a package‑specific name, a scoped named logger vialogger.WithNamedLogger("3rdparty"), andfx.Provide(NewAuthHandler)to expose the handler constructor. This matches the existing fx/logger conventions in the project and should compose cleanly with the top‑level handlers module. Based on learnings.internal/sms-gateway/modules/webhooks/models.go (1)
6-21: Confirmusers.UserGORM mapping matches previousmodels.UserSwapping the association type to
users.Useris consistent with the new users module and should work as long asusers.Userkeeps the same table name/PK type thatUserIDpoints to. Please double‑check that the new model’s GORM config (table name, primary key column/type) still matches the existingwebhooksFK/constraints soAutoMigratedoesn’t unexpectedly rewrite them.internal/sms-gateway/handlers/module.go (1)
9-10: thirdparty handlers module wiring looks consistentImporting
handlers/thirdpartyand addingthirdparty.Module()to the handlersfx.Modulecleanly integrates the new submodule; this aligns with the existingnewThirdPartyHandlerusage and keeps composition idiomatic.Also applies to: 40-41
internal/sms-gateway/app.go (1)
10-11: JWT and users modules are wired appropriately into the Fx appAdding
users.Module()andjwt.Module()to the top‑levelModuleis consistent with the rest of the DI setup and should make the new services available to handlers and middlewares. Please ensure you run the app (orfx.ValidateApp/tests) to confirm there are no missing bindings or new dependency cycles introduced by these modules.Also applies to: 24-25, 44-45, 59-60
internal/sms-gateway/handlers/devices/3rdparty.go (1)
9-13: Devices 3rd‑party handlers now depend on scopes andusers.User– verify upstream auth pipelineSwitching the handler signatures to
users.Userand wrapping the routes withpermissions.RequireScope(ScopeList|ScopeDelete)plususerauth.WithUseris consistent with the new auth/users design. This does mean:
- These endpoints now require scopes to be present in the context (set by Basic/JWT middleware via
permissions.SetScopes).- They also assume a user has been stored via
userauth.SetUserbeforeWithUserruns; otherwiseWithUserwill panic.Please double‑check the 3rd‑party router setup (
newThirdPartyHandlerand JWT/basic middlewares) to ensure that, for every security scheme advertised here (ApiAuth,JWTAuth), both the user and appropriate scopes are always set before these handlers are hit.Also applies to: 36-37, 46-48, 60-61, 72-76, 87-88
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
7-8: Basic auth → users.Service + ScopeAll wiring looks correct
NewBasicnow logs in viausers.Service, stores ausers.UserviaSetUser, and assignspermissions.ScopeAll, whileSetUser/GetUserconsistently use a value type inLocals. This resolves the earlier pointer/value mismatch and aligns Basic auth with the new permissions model.Also applies to: 16-21, 49-56, 88-90, 96-97, 99-109
internal/sms-gateway/modules/messages/service.go (1)
124-168: Switch tousers.Userlooks consistent and non-breakingUpdating
SelectStatesandGetStateto acceptusers.Userwhile still keying everything offuser.IDkeeps behavior unchanged and aligns this service with the new users domain. Callers should be updated by the compiler; no further changes needed here.internal/sms-gateway/jwt/errors.go (1)
5-12: Error set is clear and well-scopedThe exported error variables cover the main JWT failure modes cleanly and are suitable for use with
errors.Isacross the JWT subsystem and callers.internal/sms-gateway/jwt/jwt.go (1)
10-27: JWT service interface and models are well-shapedThe
Serviceinterface and theClaims/TokenInfotypes define a minimal, cohesive JWT API (issue/parse/revoke with user ID and scopes) that should integrate cleanly with handlers and middleware.internal/sms-gateway/handlers/thirdparty/auth.go (1)
17-40: Auth handler wiring looks consistent with the rest of the stackThe
AuthHandlerconstructor andRegistermethod are wired cleanly: sharedbase.Handler, per‑routererrorHandler, and per‑routepermissions.RequireScope(ScopeTokensManage)plususerauth.WithUsermatch the security model implied by the swagger comments. I don’t see functional issues in this wiring.internal/sms-gateway/handlers/mobile.go (1)
121-152: Device registration flow with usersSvc looks consistentThe refactor to:
- Prefer
userauth.GetUser(c)when present, and- Fall back to generating a username/password and creating a user via
usersSvc.Create,is consistent with the new users service design. Passing
*userintoauthSvc.RegisterDeviceand returningLogin/Passwordonly for the newly created‑user path matches the endpoint’s description. I don’t see functional problems in this section (aside from theidGeninitialization issue called out separately).internal/sms-gateway/users/service.go (2)
13-33: Service wiring is straightforward and idiomaticThe
Servicestruct andNewServiceconstructor cleanly encapsulate the repository, login cache, and logger. Dependency injection here looks consistent with the rest of the codebase.
35-69: User creation and lookup logic look solid
Createcorrectly checks for existing users, hashes the password with BCrypt, and wraps repository errors with useful context.GetByUsernameis a simple and appropriate wrapper overGetByID. These pieces look good as‑is.internal/sms-gateway/openapi/docs.go (3)
21-87: JWT token issue/ revoke endpoints now correctly documented with security and schemasThe
/3rdparty/v1/auth/tokenPOST and/3rdparty/v1/auth/token/{jti}DELETE operations now include bothApiAuthandJWTAuthsecurity plus concrete request/response/error schemas, which aligns the OpenAPI spec with the new token handlers and resolves the earlier missing‑security problem.
936-1013: Health endpoints and response schema look consistentThe new
/health/live,/health/ready, and/health/startupendpoints, together withsmsgateway.HealthResponseand related types, provide clear liveness/readiness/startup probes with 200/503 semantics. This is a solid addition for observability and Kubernetes-style health checks.Also applies to: 1230-1288
1847-1855: JWTAuth security definition and updated titleDefining
JWTAuthas anapiKeyin theAuthorizationheader and updating the Swagger title to “SMSGate API” both look appropriate and consistent with the rest of the spec and new JWT functionality.Also applies to: 1879-1884
internal/sms-gateway/jwt/config.go (1)
12-32: Config validation for secret and TTL looks solid
Validatecorrectly enforces a non-empty secret with a minimum length and rejects non‑positive TTL values, which prevents misconfigured JWT services from starting.internal/sms-gateway/jwt/service.go (2)
24-51: JWT token generation flow is well-validated and side-effect safeConstructor validation, userID/scopes/TTL checks, default TTL handling, max‑TTL capping via
min(ttl, s.config.TTL), signing with HS256, and persisting the token through the repository all look correct. Error paths are wrapped with context, and issuance metrics are updated consistently based on success vs error.Also applies to: 53-112
114-161: Token parsing, validation, and revocation checks look correct
ParseTokenenforces expiry, issued-at, issuer, and algorithm, then consults the repository to fail revoked JTIs, with validation metrics updated accordingly. This matches typical JWT best practices and integrates cleanly with your revocation store.internal/sms-gateway/jwt/repository.go (1)
10-47: Repository methods correctly encapsulate token persistence and revocation checks
Insert,Revoke, andIsRevokeduseWithContext, narrow queries ontokenModel, and wrap underlying GORM errors with descriptive messages. The revocation update is idempotent and theIsRevokedcheck is simple and efficient.internal/sms-gateway/modules/auth/service.go (1)
24-36: Auth service wiring to users/devices/online modules looks cleanThe updated
Servicestruct andNewconstructor neatly injectusersSvc,devicesSvc,onlineSvc, andlogger, and keep the one-time-code cache localized. This simplifies auth to rely on the new users module without extra repositories or caches.Also applies to: 38-60
internal/sms-gateway/jwt/metrics.go (1)
8-81: JWT metrics wiring looks solid and idiomaticThe metrics setup here is clean:
- Namespaces/subsystem are consistent (
sms/auth).- Counters and histograms are clearly separated per operation (issuance/validation/revocation).
- Helper methods (
Increment*,Observe*) keep call sites simple and reduce copy‑paste of metric logic.Assuming the alerting and dashboards are now aligned with the single
statuslabel, this is good to go.Please confirm that your Prometheus alert rules and dashboards reference these exact metric names and the
statuslabel only (or update them accordingly). For example:Also applies to: 83-117
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
pkg/cache/options.go (2)
5-73: Set options (Option,WithTTL,WithValidUntil) look correct; maybe clarify “no expiry” semanticsThe data model and helpers are straightforward and match the comments:
WithTTLusingtime.Now().Add(ttl)on apply and treatingttl <= 0as “no expiry” viatime.Time{}is consistent with the documentation, andWithValidUntilis a simple override.One potential improvement: callers only see that “TTL <= 0 means the item will not expire,” but the implementation encodes that specifically as
validUntil.IsZero(). It would help to call that out explicitly in the docstring (and ensure the concrete caches consistently interprettime.Time{}as “no expiration”), and perhaps mention thatWithValidUntilcannot currently express “no expiry” and thatWithTTL(0)is the supported way to get that behavior.
107-208: GetOption helpers are implemented correctly; consider documenting interactions and edge casesThe
AndSetTTL/AndUpdateTTL/AndSetValidUntil/AndDefaultTTL/AndDeletehelpers are all thin, correct setters ongetOptionsand the pointer‑capture pattern forttl/validUntilis idiomatic in Go (the closure causes escape to heap, so there’s no lifetime issue).Two minor API/contract points you might want to tighten up:
- The docs for
AndSetTTLandAndUpdateTTLdon’t mention how non‑positive TTLs are interpreted, even thoughWithTTLclearly definesttl <= 0as “no expiry.” If the underlying Get path uses the same convention, mirroring that in the comments (or explicitly stating if they are rejected/treated differently) would avoid surprises.- When multiple
GetOptions are combined (e.g.,AndSetTTL+AndSetValidUntil+AndDefaultTTLorAndDeleteplus a TTL update), the effective precedence is delegated to the consumers ofgetOptions. It might be worth documenting the intended precedence or combination rules somewhere nearGetOptionso callers know what to expect.Aside from those clarifications, the surface looks clean and composable.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
pkg/cache/options.go(4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Lint
- GitHub Check: Analyze (go)
🔇 Additional comments (1)
pkg/cache/options.go (1)
75-105:getOptionsplumbing andisEmptyhelper are solidThe
getOptionsstruct andapply/isEmptyhelpers are minimal and correctly model optional behavior via pointer fields and booleans.isEmptychecks all flags, so consumers can cheaply skip post‑processing when noGetOptions were supplied.No functional issues here from what’s visible.
70e753c to
d32510b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
33-42: Document the new 403 “forbidden” responses introduced by scope checksEach endpoint now uses
permissions.RequireScope(...), which will return403 Forbiddenwhen the user lacks the required scope. The Swagger annotations for list/post/delete webhooks still only declare 401 and 500 failures.Consider adding a
403error response to all three endpoints’ annotations so the OpenAPI spec matches the actual behavior.Also applies to: 54-66, 87-97
internal/sms-gateway/handlers/messages/3rdparty.go (1)
243-258:/messages/inbox/exportlacks a permission scope guard unlike other message endpointsIn
Register, all 3rd‑party message routes except inbox export are wrapped withpermissions.RequireScope(...):
GET ""→ScopeListPOST ""→ScopeSendGET ":id"→ScopeReadPOST "inbox/export"→ onlyuserauth.WithUser(h.postInboxExport)So
/3rdparty/v1/messages/inbox/exportis authenticated (ApiAuth/JWTAuth) but not scope‑restricted, which is inconsistent with the rest of the messages API and could allow users without the expected permission to trigger exports.Consider guarding this route with an appropriate scope (e.g.,
ScopeReador a dedicated export scope) to keep the authorization model consistent:func (h *ThirdPartyController) Register(router fiber.Router) { - router.Post("inbox/export", userauth.WithUser(h.postInboxExport)) + router.Post("inbox/export", permissions.RequireScope(ScopeRead), userauth.WithUser(h.postInboxExport)) }(or whatever scope name best matches your intent).
Also applies to: 280-286
pkg/cache/memory.go (1)
177-241: Fix TTL update semantics for items without TTL and caches with zero default TTLIn
memoryCache.Get, the TTL update block has two problematic edge cases:
- When an item has no TTL (
item.validUntil.IsZero()),AndUpdateTTLdoesitem.validUntil = item.validUntil.Add(*o.updateTTL). This computes a timestamp relative to the zero time, so the entry becomes effectively “always expired” andgetItemwill returnErrKeyExpiredinstead of extending its lifetime.- When the cache was created with
NewMemory(0)(documented as “no expiration by default”),AndDefaultTTLstill executesitem.validUntil = time.Now().Add(m.ttl). Withm.ttl == 0, that setsvalidUntilto “now”, making the entry expire almost immediately, which contradicts the “no expiration” default.A minimal fix is to special‑case these two situations:
- Treat
AndUpdateTTLon an item with no existing TTL as “set a fresh TTL from now”.- Treat
AndDefaultTTLwithm.ttl <= 0as “clear the TTL” (no expiration), matchingNewMemoryandWithTTLsemantics.For example:
func (m *memoryCache) Get(_ context.Context, key string, opts ...GetOption) ([]byte, error) { return m.getValue(func() (*memoryItem, bool) { @@ - o := getOptions{} - o.apply(opts...) + o := getOptions{} + o.apply(opts...) @@ - m.mux.Lock() + m.mux.Lock() item, ok := m.items[key] @@ - } else if o.delete { + } else if o.delete { delete(m.items, key) - } else if !item.isExpired(time.Now()) { - if o.validUntil != nil { - item.validUntil = *o.validUntil - } else if o.setTTL != nil { - item.validUntil = time.Now().Add(*o.setTTL) - } else if o.updateTTL != nil { - item.validUntil = item.validUntil.Add(*o.updateTTL) - } else if o.defaultTTL { - item.validUntil = time.Now().Add(m.ttl) - } - } + } else if !item.isExpired(time.Now()) { + now := time.Now() + if o.validUntil != nil { + item.validUntil = *o.validUntil + } else if o.setTTL != nil { + item.validUntil = now.Add(*o.setTTL) + } else if o.updateTTL != nil { + if item.validUntil.IsZero() { + // No existing TTL: treat as setting a fresh TTL from now. + item.validUntil = now.Add(*o.updateTTL) + } else { + item.validUntil = item.validUntil.Add(*o.updateTTL) + } + } else if o.defaultTTL { + if m.ttl > 0 { + item.validUntil = now.Add(m.ttl) + } else { + // Zero/negative default TTL means "no expiration". + item.validUntil = time.Time{} + } + } + } @@ }) }This keeps the fast path unchanged, preserves existing behavior for items that already have a TTL, and makes
AndUpdateTTL/AndDefaultTTLbehave sensibly for “no TTL” entries andNewMemory(0)caches. Based on learnings, this also keeps cleanup/Drain semantics intact because expiration is still driven solely byvalidUntil.pkg/cache/redis.go (1)
24-48: Fix Redis TTL edge cases:AndDefaultTTLwith zero TTL andAndUpdateTTLwithout existing TTLTwo TTL bugs confirmed in the Redis cache implementation:
AndDefaultTTLwithRedisConfig{TTL: 0}(lines 306–307)
SettingttlTimestamp = time.Now().Add(r.ttl).Unix()whenr.ttl == 0produces the current Unix timestamp. This causesHExpireAtto set the field expiration to "now", immediately deleting it on next access—opposite of the intended "no default TTL" behavior.
AndUpdateTTLwhen field has no TTL (lines 34–36 in Lua script)
WhenHTTLreturns-1(field exists but has no TTL),newTtl = -1 + ttlDeltaproducesttlDelta - 1or a negative value. This is unintuitive; when a field has no TTL, extending byttlDeltashould set a fresh TTL ofttlDeltaseconds, not treat it as offset arithmetic.Missing Redis version requirement (type comment)
TheredisCachetype comment does not document that Redis 7.4+ is required for per-field hash TTL commands.Apply the fixes from the original comment, which correctly handle both edge cases.
♻️ Duplicate comments (5)
internal/sms-gateway/users/repository.go (1)
32-62: Fix GORM pointer usage inGetByIDand detect missing users inUpdatePasswordTwo important issues here, both previously flagged:
- Incorrect pointer passed to GORM in
GetByIDuser := new(userModel) if err := r.db.Where("id = ?", id).Take(&user).Error; err != nil {
useris already of type*userModel, so&useris**userModel. GORM expects a pointer to the destination struct (*userModel), not a pointer to a pointer. This can lead to incorrect behavior depending on the GORM version.Change
Take(&user)toTake(user):func (r *repository) GetByID(id string) (*userModel, error) { user := new(userModel) - if err := r.db.Where("id = ?", id).Take(&user).Error; err != nil { + if err := r.db.Where("id = ?", id).Take(user).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrNotFound } return nil, fmt.Errorf("can't get user: %w", err) } return user, nil }
UpdatePasswordsilently succeeds when no rows are updatedGORM’s
Updatedoes not return an error when zero rows are affected (e.g. missing user ID). To keep behavior consistent withGetByIDreturningErrNotFound, you should checkRowsAffected:func (r *repository) UpdatePassword(ID string, passwordHash string) error { - if err := r.db.Model((*userModel)(nil)). - Where("id = ?", ID). - Update("password_hash", passwordHash).Error; err != nil { - return fmt.Errorf("can't update password: %w", err) - } - - return nil + result := r.db.Model((*userModel)(nil)). + Where("id = ?", ID). + Update("password_hash", passwordHash) + + if err := result.Error; err != nil { + return fmt.Errorf("can't update password: %w", err) + } + if result.RowsAffected == 0 { + return ErrNotFound + } + + return nil }These fixes align repository behavior with the rest of the users module and avoid hard-to-debug inconsistencies.
Please confirm against your GORM version’s docs that
Takeshould receive*userModel(not**userModel) and thatRowsAffectedis the correct way to detect missing rows afterUpdate.internal/sms-gateway/jwt/config.go (1)
12-16: Optional: decide whetherIssuershould be validated or explicitly documented as optional
Config.Issueris present but not validated inValidate(). Since the service uses this when building JWT claims, consider either:
- Adding a non-empty check (similar to
Secret) if an issuer is required for your tokens, or- Adding a brief comment/doc note indicating that an empty issuer is intentionally allowed.
This mirrors a previous comment and is mostly about making the configuration contract explicit.
internal/sms-gateway/handlers/thirdparty/auth.go (1)
42-45: Clarifyttlsemantics, add bounds, and align with OpenAPI definition
tokenRequest.TTLis auint64in (implicit) seconds and is passed as:time.Duration(req.TTL) * time.SecondThere are a few problems here:
- Very large
ttlvalues can overflowtime.Duration(int64) during the cast/multiply, silently producing incorrect expiries.ttl == 0is accepted but its meaning (“no expiry”, “use default”, or “immediately expired”) isn’t defined or validated.- The generated OpenAPI currently models
ttlastime.Duration(nanoseconds) via#/definitions/time.Duration, while the handler interprets it as seconds, so clients and server disagree on units.Consider:
- Adding explicit validation on
TTL(e.g., reject 0/negative-equivalent and enforce an upper bound before casting), and- Making the units and behavior explicit and consistent between handler and docs (either keep seconds everywhere or switch the field type to
time.Durationand treat it as such, then regenerate Swagger).Also applies to: 76-76
internal/sms-gateway/handlers/mobile.go (1)
43-74: Still ignoringnanoid.Standarderror; handle init failure explicitly
idGen, _ := nanoid.Standard(21)ignores the returned error. If initialization ever fails and returns a nil function, subsequent calls toh.idGen()will panic at runtime.You should either fail fast or log+exit if ID generation can’t be initialized, rather than constructing a handler with a broken generator. For example:
func newMobileHandler( @@ - idGen, _ := nanoid.Standard(21) + idGen, err := nanoid.Standard(21) + if err != nil { + // Fatal here is acceptable since the handler cannot function without IDs. + logger.Fatal("failed to initialize ID generator", zap.Error(err)) + } @@ - idGen: idGen, + idGen: idGen, } }Or, alternatively, change
newMobileHandlerto return(*mobileHandler, error)and propagate the error to the caller.pkg/cache/redis.go (1)
74-87: Document minimum Redis version required for hash TTL commandsThis implementation relies on Redis per-field hash TTL commands (
HEXPIRE*,HTTL, etc.) and theirFIELDSsub-arguments. Older Redis versions will either fail these commands or not support them at all, leading to runtime errors that are hard for integrators to diagnose.It would help to make the minimum Redis version requirement explicit in the
redisCachetype documentation, for example:-// redisCache implements the Cache interface using Redis as the backend. -// -// This implementation stores all data in a Redis hash, with each cache item -// being a field in the hash. It uses Redis's built-in TTL functionality for -// expiration and Lua scripts for atomic operations. +// redisCache implements the Cache interface using Redis as the backend. +// +// This implementation stores all data in a Redis hash, with each cache item +// being a field in the hash. It uses Redis's built-in TTL functionality for +// expiration and Lua scripts for atomic operations. +// +// NOTE: This cache relies on Redis per-field hash TTL commands (e.g. HEXPIRE, +// HEXPIREAT/HEXPIRETIME, HTTL/HPTTL). Ensure your Redis deployment is recent +// enough to support these commands (Redis 7.4+), or use the in-memory cache +// instead.This mirrors the earlier review feedback and makes the requirement visible to operators configuring the service. Please verify the exact minimum Redis version and command names against official Redis documentation before finalizing the wording.
🧹 Nitpick comments (16)
internal/sms-gateway/modules/messages/service.go (2)
135-168: GetState: user-scoped cache/DB lookups are correct; consider passing context instead of BackgroundUsing
user.IDconsistently for both cache keying and the repository filter keeps message state strictly scoped to the current user, and the negative-caching ofErrMessageNotFoundavoids repeated DB hits. The behavior is sound.Non-blocking: you might eventually want
GetStateto accept acontext.Contextand thread it through tocache.Get/cache.Setinstead of usingcontext.Background(), so callers can propagate deadlines and cancellations from HTTP handlers or workers.
229-239: Align Enqueue cache key with generated ExtID to avoid wasted cache entriesIn
Enqueue, whenmessage.IDis empty you generate a new external ID:if msg.ExtID == "" { msg.ExtID = s.idgen() } state.ID = msg.ExtIDbut the cache entry is still written under
message.ID:if err := s.cache.Set(context.Background(), device.UserID, message.ID, anys.AsPointer(modelToMessageState(msg))); err != nil { ... }If
message.ID == "", this writes a value under the("", userID)key thatGetState(user, state.ID)will never read, causing an avoidable DB hit on the first lookup and a “dummy” cache entry for the empty ID.Consider switching the cache key to
msg.ExtID(orstate.ID) so it always matches whatGetStatewill query:- if err := s.cache.Set(context.Background(), device.UserID, message.ID, anys.AsPointer(modelToMessageState(msg))); err != nil { + if err := s.cache.Set(context.Background(), device.UserID, msg.ExtID, anys.AsPointer(modelToMessageState(msg))); err != nil {This keeps cache keys consistent regardless of whether the client supplied an ID.
internal/sms-gateway/users/models.go (1)
10-27: User persistence model looks good; clarify cascade behavior for devices
userModelitself andMigratelook fine and consistent withDevice.UserID(varchar(32)). The commented-outDevices []models.Devicerelation withOnDelete:CASCADEmeans there is currently no DB-level cascade from users to devices; any cleanup must be handled in application code.If you intend DB-enforced cascade, consider either:
- Re‑introducing the relation without
gorm:"-", or- Moving the
constraint:OnDelete:CASCADEtag ontoDevice.UserID.Otherwise, consider deleting the commented field to avoid confusion.
Please confirm your intended deletion semantics and adjust the GORM tags accordingly if you want DB-level cascade.
internal/sms-gateway/modules/auth/service.go (2)
27-59: Service wiring is good;idgennow appears unusedInjecting
usersSvc,devicesSvc, andonlineSvcviaNewlooks clean and makes the dependencies explicit. However,idgenis still constructed and stored but not used anywhere in this file, which suggests leftover state from the previous design.If token/ID generation has been fully delegated to
devicesSvc, consider:
- Removing the
idgenfield and thenanoid.Standard(21)call, and- Dropping the
github.com/jaevor/go-nanoiddependency from this module if nothing else uses it.This will simplify the service and avoid unnecessary RNG work at startup.
Please confirm whether
idgenis used elsewhere in this package or can be safely removed.
90-97: Check semantics of user identifier passed through one-time codes
RegisterDevicenow takes ausers.Userand delegates device creation todevicesSvc.Insert, which is a nice separation of concerns.In
AuthorizeUserByCode:userID, err := s.codesCache.GetAndDelete(code) ... user, err := s.usersSvc.GetByUsername(userID)The variable is named
userID, but it is passed toGetByUsername. This is fine if, in your domain, the value cached inGenerateUserCodeis actually a username and not a database ID; otherwise it risks subtle mismatches.Two concrete suggestions:
- If the cached value is a username, rename variables/parameters (e.g.
username string) for clarity acrossGenerateUserCodeandAuthorizeUserByCode.- If the cached value is a primary key/ID, consider calling a
GetByID‑style method onusersSvcinstead ofGetByUsername.Clarifying this will help prevent future bugs and confusion.
Please double-check what value your handlers pass into
GenerateUserCodeand ensure it matches whatGetByUsernameexpects.Also applies to: 133-145
internal/sms-gateway/jwt/metrics.go (1)
57-79: Consider operation-specific histogram buckets.All three duration histograms use identical buckets (5ms to 1s). Token validation is typically faster (cryptographic operations only), while issuance and revocation involve database operations. Consider tailoring buckets to each operation's expected latency profile for more granular observability.
internal/sms-gateway/jwt/module.go (1)
9-22: Consider conditional metrics and repository creation.The module always provides
NewMetricsandNewRepository(line 13), even when JWT is disabled (Config.Secret empty). This creates Prometheus metrics and potentially database queries that are never used. Consider providing these dependencies conditionally or document why they should always be created.api/requests.http (1)
221-225: Remove unnecessary Content-Type header.The DELETE request includes
Content-Type: application/jsonbut has no request body. This header is unnecessary for DELETE requests without a body.internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
23-31: Consider distinguishing authentication vs system errors.Both token parsing failures and user lookup errors return
fiber.ErrUnauthorized. IfGetByUsernamecan fail due to database errors (not just "user not found"), consider logging system errors or returning 503 Service Unavailable for transient failures, reserving 401 for genuine authentication issues.internal/sms-gateway/users/service.go (1)
71-94: Ensure login cache never stores or logs plaintext passwords
Login/ChangePasswordpass the rawpasswordintologinCache.Get/Set/Delete. That’s fine at the service boundary, but it puts constraints on the cache implementation:
- It must not use the raw password directly as a cache key or value, and
- It must never log the password (even at debug) or expose it through metrics.
Please double‑check the
loginCacheimplementation and, if needed, derive opaque keys (e.g., using a hash of username+password or some other irreversible token) so no plaintext credentials are persisted or logged by the caching layer.Also applies to: 96-112
internal/sms-gateway/jwt/service.go (2)
53-112: GenerateToken validations and TTL handling are robustThe parameter validation (non-empty
userID, non-empty scopes, non-negative TTL, zero TTL → default from config) plus capping expiry atmin(ttl, s.config.TTL)gives a clear, bounded token lifetime model. The issuance path (claims construction, signing, persistence, and metrics) is consistent and free of obvious edge-case bugs.You might consider using
time.Now().UTC()forIssuedAt/ExpiresAtto make the intended timezone of JWT timestamps explicit, but it’s not required for correctness.
114-161: Consider normalizing ParseToken errors toErrInvalidTokenThe parsing path is generally correct: it enforces expiration, issued-at, issuer, and HS256-only, then checks revocation via the repository. One behavioral nuance is that parse failures return a wrapped error (
fmt.Errorf("failed to parse token: %w", parseErr)) instead of the sentinelErrInvalidToken, while structurally invalid tokens or bad claims useErrInvalidToken.If external callers or HTTP handlers are expected to branch on
ErrInvalidToken, you may want to normalize all client-facing parse failures (including signature/format issues) toErrInvalidTokenand log the underlyingparseErrinternally, to avoid leaking parsing details and to keep the public API simpler.internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
7-56: User locals handling is now consistent; consider tighteningWithUserpanic semanticsThe switch to
SetUser+GetUserstoring ausers.Uservalue inLocalsfixes the previous pointer/value mismatch and makesHasUserandWithUserconsistent across both Basic and Code auth flows. Grantingpermissions.ScopeAllon successful Basic auth also aligns with the new permissions middleware.
WithUserintentionally panics when no user is present (handler(*GetUser(c), c)), as documented. Given that your routes usingWithUseralready chainUserRequired()beforehand, this should be safe in practice. If you ever expectWithUserto be reused more freely, a small guard with a clearer panic message (or a 401) aroundGetUser(c) == nilwould make failures easier to diagnose.Also applies to: 82-109, 124-133
internal/sms-gateway/handlers/mobile.go (1)
121-153: Users service integration and auth composition look correct
- In
postDevice, reusing an authenticated user fromuserauth.GetUserand only generating username/password for new users matches the route docs (“Returns user credentials only for new users”). Passing*userintoRegisterDeviceand usingusernamein the response is consistent with the newusers.Usermodel.- Updating
getUserCodeto acceptusers.Userand wiring the route throughuserauth.NewBasic(h.usersSvc),UserRequired(), andWithUser(h.getUserCode)creates a clean, type-safe flow from Basic auth to code generation.changePasswordnow correctly delegates tousersSvc.ChangePasswordusingdevice.UserIDand logs on failure, returning a 401 to the client, which is a reasonable security posture.- Router changes that swap in
userauth.NewBasic(h.usersSvc)for the relevant endpoints are aligned with the new users module.Overall, the mobile handler’s integration with
usersSvcand the updated authentication flow looks coherent and consistent with the rest of the refactor.You already have a TODO noting that
/user/password“should be underuserauth.NewBasicprotection instead ofdeviceauth”; aligning that route with the rest of the user-centric auth would be a good follow-up once you’re ready to change its behavior.Also applies to: 186-207, 223-236, 241-263
pkg/cache/cache.go (1)
108-283: Cache interface and behavior look good; consider clarifying ErrKeyExpired semanticsThe interface shape and method docs align with both
memoryCacheandredisCacheimplementations. One small suggestion: in theGetcomment you state that an expired key yieldsErrKeyExpired, but for backends like Redis that eagerly drop expired fields, callers will effectively seeErrKeyNotFoundinstead. You may want to add a short note that the exact error for expired entries is implementation-dependent (e.g., in-memory keeps expired items untilCleanup, Redis removes them immediately).pkg/cache/options.go (1)
75-105: Clarify GetOption semantics for items without TTL and caches with zero default TTLThe
getOptionsstructure and helpers (AndSetTTL,AndUpdateTTL,AndSetValidUntil,AndDefaultTTL,AndDelete) give a nice, expressive surface. The one missing piece is documentation for edge cases:
- What should
AndUpdateTTLdo when an item has no existing TTL (e.g.,validUntilis zero in the in-memory cache, or HTTL returns “no TTL” in Redis)?- How should
AndDefaultTTLbehave when the cache’s default TTL is zero (meaning “no expiration” inNewMemory, and effectively “no default TTL” inRedisConfig)?Right now these behaviors are implicitly defined in
memoryCache.GetandredisCache.Get, and they’re a bit inconsistent. Once you address the TTL handling bugs in those implementations, consider adding 1–2 sentences to theAndUpdateTTLandAndDefaultTTLcomments to spell out the intended semantics in those edge cases so future backends can match them.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (62)
api/mobile.http(1 hunks)api/requests.http(5 hunks)cmd/sms-gateway/main.go(1 hunks)configs/config.example.yml(1 hunks)deployments/grafana/dashboards/jwt.json(1 hunks)deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)go.mod(1 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(6 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(3 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(10 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(8 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(8 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/jwt/config.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/metrics.go(1 hunks)internal/sms-gateway/jwt/models.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/repository.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/openapi/docs.go(17 hunks)internal/sms-gateway/users/cache.go(1 hunks)internal/sms-gateway/users/domain.go(1 hunks)internal/sms-gateway/users/errors.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)internal/sms-gateway/users/service.go(1 hunks)pkg/cache/cache.go(1 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(13 hunks)pkg/cache/options.go(4 hunks)pkg/cache/redis.go(10 hunks)pkg/cache/typed.go(1 hunks)
💤 Files with no reviewable changes (3)
- internal/sms-gateway/modules/auth/repository.go
- internal/sms-gateway/models/models.go
- internal/sms-gateway/modules/auth/module.go
🚧 Files skipped from review as they are similar to previous changes (26)
- configs/config.example.yml
- internal/sms-gateway/handlers/logs/permissions.go
- internal/config/module.go
- internal/sms-gateway/users/errors.go
- go.mod
- internal/sms-gateway/handlers/settings/permissions.go
- internal/sms-gateway/app.go
- internal/sms-gateway/users/domain.go
- internal/sms-gateway/jwt/disabled.go
- internal/sms-gateway/users/cache.go
- internal/sms-gateway/jwt/errors.go
- deployments/prometheus/alerts/jwt-alerts.yml
- internal/config/types.go
- internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql
- internal/sms-gateway/users/module.go
- deployments/grafana/dashboards/jwt.json
- internal/sms-gateway/handlers/messages/permissions.go
- internal/sms-gateway/modules/webhooks/models.go
- cmd/sms-gateway/main.go
- internal/sms-gateway/handlers/thirdparty/module.go
- pkg/cache/typed.go
- internal/sms-gateway/handlers/logs/3rdparty.go
- internal/sms-gateway/jwt/models.go
- internal/sms-gateway/modules/settings/models.go
- internal/sms-gateway/handlers/devices/permissions.go
- internal/config/config.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/redis.gopkg/cache/cache.gopkg/cache/memory.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/redis.gopkg/cache/cache.gopkg/cache/memory.go
🧬 Code graph analysis (20)
internal/sms-gateway/users/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-15)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
internal/sms-gateway/jwt/module.go (6)
internal/sms-gateway/jwt/metrics.go (2)
NewMetrics(34-81)Metrics(24-31)internal/sms-gateway/jwt/repository.go (2)
NewRepository(14-18)Repository(10-12)internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)internal/sms-gateway/jwt/service.go (1)
New(24-51)internal/sms-gateway/jwt/models.go (1)
Migrate(32-37)
internal/sms-gateway/handlers/module.go (2)
internal/sms-gateway/app.go (1)
Module(35-60)internal/sms-gateway/handlers/thirdparty/module.go (1)
Module(8-16)
internal/sms-gateway/handlers/thirdparty/auth.go (4)
internal/sms-gateway/handlers/thirdparty/permissions.go (1)
ScopeTokensManage(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/jwt/errors.go (4)
ErrInvalidParams(9-9)ErrInitFailed(7-7)ErrInvalidConfig(8-8)ErrDisabled(6-6)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (1)
Device(17-28)
internal/sms-gateway/users/service.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/users/errors.go (1)
ErrExists(7-7)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)pkg/cache/errors.go (1)
ErrKeyNotFound(17-17)
internal/sms-gateway/modules/messages/service.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/users/repository.go (1)
internal/sms-gateway/users/errors.go (1)
ErrNotFound(6-6)
internal/sms-gateway/handlers/webhooks/3rdparty.go (5)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/webhooks/permissions.go (3)
ScopeList(4-4)ScopeWrite(5-5)ScopeDelete(6-6)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
internal/sms-gateway/handlers/3rdparty.go (3)
internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(17-21)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
NewBasic(21-59)UserRequired(114-122)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
NewJWT(13-38)
internal/sms-gateway/jwt/config.go (1)
internal/sms-gateway/jwt/errors.go (1)
ErrInvalidConfig(8-8)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
internal/sms-gateway/handlers/middlewares/permissions/permissions.go (2)
SetScopes(15-17)ScopeAll(10-10)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/jwt/service.go (5)
internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/repository.go (1)
Repository(10-12)internal/sms-gateway/jwt/metrics.go (3)
Metrics(24-31)StatusError(20-20)StatusSuccess(19-19)internal/sms-gateway/jwt/jwt.go (2)
TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (4)
ErrInitFailed(7-7)ErrInvalidParams(9-9)ErrInvalidToken(10-10)ErrTokenRevoked(11-11)
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (4)
internal/sms-gateway/modules/auth/service.go (1)
Service(24-36)internal/sms-gateway/modules/messages/service.go (1)
Service(31-43)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
SetUser(88-90)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
SetScopes(15-17)
internal/sms-gateway/handlers/mobile.go (3)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
GetUser(102-109)
internal/sms-gateway/handlers/settings/3rdparty.go (5)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/settings/permissions.go (2)
ScopeRead(4-4)ScopeWrite(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)internal/sms-gateway/handlers/webhooks/permissions.go (1)
ScopeWrite(5-5)
internal/sms-gateway/modules/auth/service.go (2)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/models/models.go (1)
Device(17-28)
pkg/cache/cache.go (1)
pkg/cache/options.go (2)
Option(9-9)GetOption(89-89)
pkg/cache/memory.go (1)
pkg/cache/options.go (1)
AndDelete(204-208)
🪛 Gitleaks (8.29.0)
api/mobile.http
[high] 71-71: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 72-72: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Benchmark
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (30)
internal/sms-gateway/modules/messages/service.go (2)
15-15: users package import matches new user type usageImporting
github.com/android-sms-gateway/server/internal/sms-gateway/usersis consistent with the updated method signatures usingusers.User, and there’s no conflict with existingmodelsusage. Givenusers.Userexposes anID stringfield, continuing to dereferenceuser.IDis type-safe. (Based on relevant_code_snippets forinternal/sms-gateway/users/domain.go.)
124-133: SelectStates: user scoping and error context look goodOverriding
filter.UserIDwithuser.IDcorrectly enforces per-user scoping regardless of caller-supplied filters, which is important for multi-tenant safety. The new wrapped error ("can't select messages: %w") adds useful context without changing behavior for callers that unwrap.internal/sms-gateway/models/migration.go (1)
12-13: Device-only migration: ensure users/jwt migrations are invoked elsewhere
Migratenow only migratesDevice, which is fine as long asinternal/sms-gateway/users.Migrate(and any jwt/token migrations) are wired into app startup separately. Otherwise new deployments will miss theusersschema.Please double-check your app/bootstrap wiring to confirm that
users.Migrateand jwt-related migrations are called alongsidemodels.Migrate.internal/sms-gateway/handlers/webhooks/permissions.go (1)
3-7: Webhooks scope constants are clear and reusableThe scope constants are well named (
list/write/delete) and will keep handlers/OpenAPI definitions consistent.internal/sms-gateway/jwt/metrics.go (4)
8-21: LGTM!The metric name constants and status label definitions are well-structured and follow Prometheus naming conventions.
23-31: LGTM!The Metrics struct is well-designed with appropriate field types for JWT operation tracking.
83-96: LGTM!The increment methods provide a clean interface for updating JWT operation counters with appropriate status labels.
98-117: LGTM!The observe methods correctly implement duration tracking using the standard Prometheus timer pattern with defer for reliable measurement.
api/mobile.http (1)
71-72: Verify test credentials are not production secrets.The password values appear hardcoded in this test request file. Ensure these are dummy credentials for development/testing only and not production secrets.
Based on static analysis hints flagging potential API keys.
internal/sms-gateway/handlers/thirdparty/permissions.go (1)
1-5: LGTM!The permission scope constant follows the established naming pattern and will be used for JWT-based access control on token management endpoints.
internal/sms-gateway/jwt/module.go (1)
24-26: LGTM!The migration registration in init() ensures the JWT tokens table is created during database initialization.
internal/sms-gateway/handlers/module.go (1)
9-9: LGTM!The thirdparty module is correctly wired into the handlers composition, following the established pattern for module integration.
Also applies to: 40-40
internal/sms-gateway/handlers/devices/3rdparty.go (2)
9-9: LGTM!The migration from
models.Usertousers.Userand the addition of JWT authentication in OpenAPI specs are consistent with the broader JWT integration. The changes properly align handler signatures with the new user domain type.Also applies to: 12-12, 36-36, 46-46, 60-60, 72-72
87-88: LGTM!The permission scopes are correctly enforced using middleware composition. The middleware chain properly validates scopes before invoking handlers.
api/requests.http (2)
4-4: LGTM!The JWT token environment variable follows the established pattern for request configuration.
38-39: LGTM!The Bearer token authentication additions provide flexibility for testing JWT-based auth while preserving Basic auth as fallback options. This approach facilitates validation of both authentication methods.
Also applies to: 83-84, 88-89, 108-110
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (2)
13-22: LGTM!The Bearer token extraction logic correctly handles the Authorization header with case-insensitive matching and safely strips the prefix. The middleware appropriately delegates to the next handler when a Bearer token is not present, allowing other authentication methods to process the request.
33-37: LGTM!The middleware correctly stores the authenticated user and token scopes in the request context, making them available to downstream handlers via the established userauth and permissions patterns.
internal/sms-gateway/handlers/3rdparty.go (2)
20-67: Explicit constructor & dependency wiring look solidThe move to an explicit constructor with
usersSvc,jwtSvc, and the various controllers (includingauthHandler) is clean and makes the handler’s dependencies very clear. The baseHandlerinitialization is consistent with the rest of the codebase, and keepingjwtServiceonly as a constructor parameter while storingjwtSvcis fine.
72-81: Auth middleware chain for /3rdparty/v1 (including /auth) is coherent—verify it matches your intent
router.Use(userauth.NewBasic(h.usersSvc), jwtauth.NewJWT(h.jwtSvc, h.usersSvc), userauth.UserRequired())ensures that all routes registered after this (including/auth,/messages,/webhooks, etc.) require either valid Basic or Bearer credentials and a resolved user. Health remains unauthenticated since it’s registered before theUse, which is typically desirable.Please double‑check that
/3rdparty/v1/authis indeed meant to require an already authenticated user (Basic or JWT) and that you don’t need a more permissive path just for initial token issuance.internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
1-36: Scope storage and enforcement middleware are well‑structuredUsing Fiber locals plus
HasScope/RequireScopegives a clear, composable pattern for scope checks, and recognizingScopeAllinHasScopecentralizes the “super‑scope” behavior nicely. The comma‑ok type assertion protects against bad locals values, so there’s no panic risk here.internal/sms-gateway/jwt/jwt.go (1)
10-27: JWT service interface and claims shape align with middleware usageThe
Serviceinterface andClaims/TokenInfotypes are minimal and to the point, and they line up with howjwtauth.NewJWTusesParseTokenand theUserID/Scopesfields. Embeddingjwt.RegisteredClaimskeeps you compatible with the underlying library’s validation model.internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
45-52: Route-level scope checks combined withWithUserare wired correctlyThe updated handlers take
user users.Userand are only invoked viauserauth.WithUser, while per-route authorization is enforced withpermissions.RequireScope(ScopeList/ScopeWrite/ScopeDelete). Given the upstream/3rdparty/v1middleware chain (Basic/JWT + UserRequired), this results in a clear, layered auth model: authenticate → require user → require scope → invoke handler with a strongly typed user.Also applies to: 69-85, 100-108, 110-113
internal/sms-gateway/jwt/config.go (1)
18-31: Config validation for Secret and TTL is now robustThe
Validate()method correctly enforces a non-empty secret, a minimum length of 32 bytes, and a strictly positive TTL, which should prevent most misconfiguration errors at startup.internal/sms-gateway/users/service.go (1)
35-94: Overall users.Service flows look correctUser creation, lookup, login verification with BCrypt, and best-effort login caching are wired cleanly; error wrapping preserves root causes while keeping messages meaningful. No functional issues stand out in these methods.
internal/sms-gateway/handlers/settings/3rdparty.go (1)
36-40: Settings 3rd‑party handlers now correctly use JWT + scoped permissionsUsing
users.Userin handler signatures, adding@Security JWTAuthto the docs, and wiring routes throughpermissions.RequireScope(ScopeRead/ScopeWrite)plususerauth.WithUsergives clear, consistent access control on settings without changing core behavior.Also applies to: 48-90, 92-124, 126-129
internal/sms-gateway/jwt/service.go (2)
24-51: Constructor wiring and ID factory handling look solid
NewvalidatesConfig, enforces non-nilRepository/Metrics, and correctly propagatesnanoid.Standarderrors instead of risking a nilidFactory. The resultingservicestruct wiring is cohesive and idiomatic for dependency injection; no changes needed here.
163-177: Revocation flow and metrics are straightforward
RevokeTokencleanly delegates toRepository.Revokeand records revocation metrics based on error presence. Treating “no matching token” as a successful no-op is a reasonable, idempotent behavior for this kind of endpoint.internal/sms-gateway/jwt/repository.go (1)
14-47: Token repository implementation looks correct and idiomaticThe GORM usage for insert, revocation (single UPDATE with
revoked_at is nullguard), and revocation checks via COUNT is straightforward and safe. Error wrapping adds useful context without leaking SQL details. I don’t see any functional or concurrency issues here.pkg/cache/errors.go (1)
5-50: Error documentation is consistent and helpfulThe expanded comments for
ErrKeyNotFound,ErrKeyExpired, andErrKeyExistsaccurately describe how the in-memory and Redis caches use these errors, and the examples read well. No changes needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
internal/sms-gateway/handlers/thirdparty/auth.go (1)
59-76: Validatettlto avoidtime.Durationoverflow and clarify zero/huge TTL behavior
req.TTLis converted with:time.Duration(req.TTL) * time.SecondWithout bounds, very large
ttlvalues can overflowtime.Duration(int64) and yield incorrect or even negative lifetimes. The semantics forttl == 0are also unclear (immediately expired vs. “use default TTL”).Consider:
- Adding explicit validation for
ttl(e.g., reject<= 0, and cap it at a sane upper bound before converting), and- Clearly defining how
0should behave (either reject with 400 or treat it as “use configured default”).You can enforce this either via
validatortags onsmsgateway.TokenRequestor an explicit check here before callingGenerateToken.
🧹 Nitpick comments (3)
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
24-35: Verify identity semantics between JWT claims and users service
claims.UserIDis passed intousersSvc.GetByUsername, and a similar pattern exists inauth.Service.AuthorizeUserByCode, where auserIDstring is also resolved viaGetByUsername. If this value is actually a stable user ID (not a login/username), consider:
- Switching to a dedicated
GetByID-style lookup onusersSvc, or- Renaming the claim/variables and service method to consistently reflect that this is a username.
This will avoid subtle bugs if you later distinguish between immutable IDs and changeable usernames, and it keeps the token → user resolution unambiguous.
internal/sms-gateway/modules/auth/service.go (2)
60-74:for range 3requires Go 1.22+; consider a more portable loopThe
for range 3construct is only supported in newer Go versions. If this project targets or may be built with Go < 1.22, this will not compile. A classic counted loop is more portable and equally clear:- for range 3 { + for i := 0; i < 3; i++ {The rest of the logic (retrying on
rand.ReadorSetOrFailfailures) can remain unchanged.
125-138: Align one-time-code user lookup with users service identity model
AuthorizeUserByCodepulls auserIDstring fromcodesCacheand then calls:user, err := s.usersSvc.GetByUsername(userID)This mirrors the pattern in
jwtauth.NewJWT, whereclaims.UserIDis also resolved viaGetByUsername. If the cached value and claim are truly immutable IDs (not usernames), it would be clearer and safer to:
- Introduce an ID-based lookup on
usersSvc(e.g.,GetByID), and store IDs in the cache/claims, or- Rename these fields/methods to consistently reflect that they are usernames, not IDs.
That way, future schema changes (e.g., distinct user IDs vs. usernames) won’t silently break these auth paths.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (8)
go.mod(2 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(5 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/openapi/docs.go(31 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/sms-gateway/handlers/webhooks/3rdparty.go
🧰 Additional context used
🧬 Code graph analysis (1)
internal/sms-gateway/modules/auth/service.go (2)
internal/sms-gateway/cache/factory.go (1)
Cache(14-14)internal/sms-gateway/users/domain.go (1)
User(5-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: E2E
- GitHub Check: Lint
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Analyze (go)
🔇 Additional comments (6)
go.mod (2)
18-18: Dependency version is appropriate; no changes needed.The version
v5.3.0is the latest stable release ofgolang-jwt/jwt/v5and addresses the critical DoS vulnerability (CVE-2025-30204). No security advisories block this version.
60-60: No issues found—JWT version coherence verified.The codebase uses only
github.com/golang-jwt/jwt/v5in all JWT-related imports (found ininternal/sms-gateway/jwt/service.goandinternal/sms-gateway/jwt/jwt.go). No v4 imports exist, confirming consistent behavior and no confusion risk despite v4 being present as an indirect dependency.internal/sms-gateway/openapi/docs.go (1)
21-87: Generated OpenAPI changes align with new JWT/token flowThe new
/3rdparty/v1/auth/tokenand/3rdparty/v1/auth/token/{jti}paths,JWTAuthsecurity definition, andsmsgateway.TokenRequest/TokenResponseschemas all look consistent with the handlers inthirdparty/auth.go. Since this file is generated, no manual tweaks are needed as long as swagger annotations stay in sync with behavior.Also applies to: 1787-1827, 1903-1912, 1935-1946
internal/sms-gateway/handlers/messages/permissions.go (1)
4-13: Message permission scope constants look goodScope names and values (
messages:send/read/list/export) are clear and match how they’re used in the 3rd‑party messages router.internal/sms-gateway/handlers/thirdparty/auth.go (1)
100-119: JWT error mapping middleware is well-scopedWrapping the auth routes with
errorHandlerand translatingjwt.ErrInvalidParams,ErrInitFailed/ErrInvalidConfig, andErrDisabledinto precise HTTP statuses and user-friendly messages is a solid pattern; it keeps handlers lean and centralizes JWT error semantics.internal/sms-gateway/handlers/messages/3rdparty.go (1)
46-66: JWT + scope wiring for 3rd‑party messages endpoints looks consistentThe added
@Security JWTAuth+ 403 responses, updated handler signatures tousers.User, and router wiring:
ScopeSendfor POST/3rdparty/v1/messagesScopeListfor GET/3rdparty/v1/messagesScopeReadfor GET/3rdparty/v1/messages/{id}ScopeExportfor POST/3rdparty/v1/messages/inbox/exportare all consistent with the new
messagespermission constants and the generated OpenAPI spec. Behaviorally this should enforce both authentication and fine-grained scopes on all message operations.Also applies to: 178-199, 216-232, 246-263, 284-290
185514c to
5502158
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
internal/sms-gateway/handlers/mobile.go (1)
56-56: Handle the error fromnanoid.Standard.The error from
nanoid.Standard(21)is silently ignored. If initialization fails and returns a nil function, callingh.idGen()at line 146 will cause a nil pointer panic.Apply this diff to handle the error:
- idGen, _ := nanoid.Standard(21) + idGen, err := nanoid.Standard(21) + if err != nil { + panic(fmt.Sprintf("failed to initialize ID generator: %v", err)) + }
🧹 Nitpick comments (3)
internal/sms-gateway/modules/auth/service.go (2)
96-106: Consider validatingPrivateTokenconfiguration for non‑public mode
AuthorizeRegistrationassumesconfig.PrivateTokenis correctly set whenMode != ModePublic. If it is left empty, all non‑empty tokens will be rejected (and only an empty token would pass), which can be a subtle misconfiguration. Consider adding a startup‑time config validation (or at least a warning log) when mode is private butPrivateTokenis blank.
126-135: Clarify naming for value passed intoGetByUsername
userIDfromcodesCache.GetAndDelete(code)is passed tos.usersSvc.GetByUsername(userID), but perinternal/sms-gateway/users/service.gothis parameter is conceptually a username/login, not an internal ID. Renaming the parameter and local variable tousername(or similar) would better reflect intent and avoid confusion for future readers.internal/sms-gateway/handlers/3rdparty.go (1)
69-81: Verify JWT middleware behavior for Basic‑only flows and/authroutesAll non‑health routes now share this chain:
userauth.NewBasic(h.usersSvc)jwtauth.NewJWT(h.jwtSvc, h.usersSvc)userauth.UserRequired()For endpoints that should be reachable with either Basic or JWT (notably
/3rdparty/v1/auth),jwtauth.NewJWTshould behave as an additive/optional authenticator: it should only attach a user when a valid JWT is present and avoid hard‑failing on a missingAuthorization: Bearerheader, so that Basic can still succeed and satisfyUserRequired().If any
/authendpoints are intended to be accessible without prior Basic/JWT auth (e.g., token issuance by some other credential), you may want to register them on a router group that does not include this middleware stack.Please double‑check the jwtauth middleware semantics and adjust if it currently rejects requests that rely solely on Basic auth.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (35)
cmd/sms-gateway/main.go(1 hunks)configs/config.example.yml(1 hunks)go.mod(2 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(6 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(3 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(8 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(5 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/openapi/docs.go(31 hunks)
💤 Files with no reviewable changes (3)
- internal/sms-gateway/modules/auth/module.go
- internal/sms-gateway/modules/auth/repository.go
- internal/sms-gateway/models/models.go
🚧 Files skipped from review as they are similar to previous changes (14)
- internal/sms-gateway/handlers/messages/permissions.go
- internal/sms-gateway/handlers/module.go
- internal/sms-gateway/handlers/thirdparty/auth.go
- internal/sms-gateway/modules/webhooks/models.go
- cmd/sms-gateway/main.go
- internal/config/module.go
- internal/sms-gateway/handlers/webhooks/permissions.go
- internal/sms-gateway/handlers/logs/permissions.go
- internal/sms-gateway/modules/settings/models.go
- internal/sms-gateway/app.go
- internal/sms-gateway/handlers/middlewares/permissions/permissions.go
- internal/sms-gateway/handlers/thirdparty/permissions.go
- internal/sms-gateway/models/migration.go
- go.mod
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/handlers/thirdparty/module.go
🧬 Code graph analysis (10)
internal/sms-gateway/modules/messages/service.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/handlers/logs/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/logs/permissions.go (1)
ScopeRead(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (2)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
SetUser(88-90)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
SetScopes(15-17)
internal/sms-gateway/handlers/settings/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/settings/permissions.go (2)
ScopeRead(4-4)ScopeWrite(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
internal/sms-gateway/handlers/messages/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/messages/permissions.go (4)
ScopeList(10-10)ScopeSend(6-6)ScopeRead(8-8)ScopeExport(12-12)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
internal/sms-gateway/handlers/mobile.go (2)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
GetUser(102-109)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
internal/sms-gateway/modules/auth/service.go (1)
Service(23-33)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
SetScopes(15-17)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/config/config.go (1)
internal/config/types.go (1)
Duration(11-11)
internal/sms-gateway/handlers/thirdparty/module.go (4)
internal/sms-gateway/app.go (1)
Module(35-60)internal/sms-gateway/handlers/module.go (1)
Module(16-41)internal/sms-gateway/modules/auth/module.go (1)
Module(10-29)internal/sms-gateway/handlers/thirdparty/auth.go (1)
NewAuthHandler(24-35)
internal/sms-gateway/modules/auth/service.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Test
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (22)
internal/sms-gateway/handlers/thirdparty/module.go (1)
8-16: LGTM! Clean module structure.The module follows the established fx.Module pattern and correctly uses
logger.WithNamedLoggerfor module-scoped logging. The wiring is straightforward and aligns with other modules in the codebase.internal/sms-gateway/handlers/settings/permissions.go (1)
3-6: LGTM! Clear permission scope definitions.The scope constants follow a consistent
resource:actionnaming pattern and integrate cleanly with the new permissions middleware framework.internal/sms-gateway/handlers/devices/permissions.go (1)
3-6: LGTM! Consistent scope definitions.The permission scopes follow the same
resource:actionpattern as other modules, ensuring consistency across the codebase.configs/config.example.yml (2)
41-44: LGTM! Clear JWT configuration.The JWT configuration block is well-documented with appropriate defaults. The comment clearly indicates that leaving the secret empty disables JWT functionality, which provides a safe default for existing deployments.
50-56: LGTM! Comment clarifications.The updated comments remove the "in hours" phrase, making them more generic and consistent with the use of duration types.
internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
46-53: LGTM! Correct user type migration.The handler signature correctly migrates from
models.Usertousers.User, aligning with the new users domain model.
114-116: LGTM! Proper middleware ordering.The routes correctly apply permission scopes before the user authentication handler, ensuring proper authorization flow.
internal/config/config.go (2)
91-95: LGTM! Well-structured JWT configuration.The JWT struct uses appropriate types (Duration for TTL, string for Secret/Issuer) and includes proper YAML/envconfig tags for configuration binding.
131-134: LGTM! Sensible defaults.The default JWT configuration provides reasonable values:
- 24-hour token TTL balances security with usability
- Empty secret disables JWT by default (safe for existing deployments)
- Default issuer "sms-gate.app" provides a reasonable identifier
internal/sms-gateway/handlers/devices/3rdparty.go (1)
46-55: LGTM! Correct user type migration.The handler signature correctly migrates from
models.Usertousers.User, consistent with the broader refactoring.internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
14-42: The middleware code is correct—no fixes required.The verification confirms that
claims.UserIDworks properly withGetByUsername(). The system design stores the username as theUser.IDfield (set during user creation in line 36:ID: username). While the naming could be clearer—UserIDsemantically suggests an identifier separate from username—the middleware functions correctly becauseUser.IDand username are equivalent in this codebase. TheGetByUsername()method internally callsGetByID()with the same value.internal/sms-gateway/handlers/logs/3rdparty.go (1)
5-7: LGTM! JWT authentication and permissions integration looks good.The integration of JWT authentication and scope-based permissions is well-structured:
- User type correctly migrated to
users.User- JWTAuth security annotation properly added
- Permission scope (
ScopeRead) enforced before authentication- Changes align with the broader JWT subsystem introduction
Also applies to: 28-28, 40-40, 45-45
internal/config/types.go (1)
11-48: LGTM! Well-designed Duration wrapper type.The implementation is clean and follows Go best practices:
- Nil-safe accessor methods with reasonable defaults
- Proper error wrapping in unmarshal methods
- Compile-time interface assertions
- Consistent delegation from UnmarshalYAML to UnmarshalText
internal/sms-gateway/handlers/mobile.go (1)
19-19: LGTM! User service integration and type migration executed correctly.The changes successfully integrate the new users service and migrate from
models.Usertousers.User:
- Constructor properly accepts and wires
usersSvc- User creation and authentication delegated to
usersSvc- Password change flow updated to use
usersSvc.ChangePassword- All handler signatures and routing consistently updated
Also applies to: 32-32, 43-74, 122-152, 197-197, 230-232, 242-242, 260-260
internal/sms-gateway/handlers/settings/3rdparty.go (1)
8-8: LGTM! JWT authentication and granular permission scopes properly applied.The integration is well-structured with appropriate scope differentiation:
ScopeReadfor GET operationsScopeWritefor PUT and PATCH operations- User type consistently migrated to
users.User- Security annotations properly added to all endpoints
Also applies to: 12-12, 39-39, 48-48, 60-60, 72-72, 95-95, 107-107, 127-129
internal/sms-gateway/modules/messages/service.go (1)
15-15: LGTM! Method signatures correctly updated for user type migration.The changes are straightforward and consistent:
- Import added for the new users package
- Method signatures updated to accept
users.User- No logic changes, maintaining existing behavior
Also applies to: 124-124, 135-135
internal/sms-gateway/openapi/docs.go (1)
1-1946: LGTM! Generated OpenAPI documentation reflects JWT integration correctly.This file is auto-generated by swaggo/swag and correctly reflects:
- New token management endpoints (
/3rdparty/v1/auth/token)- JWTAuth security definition and integration across endpoints
- New health probe endpoints (live, ready, startup)
- TokenRequest/TokenResponse schema definitions
- 403 Forbidden responses where appropriate
The past issues regarding security annotations and TTL type mismatches have been addressed. Any further changes should be made in the source Swagger annotations, not in this generated file.
internal/sms-gateway/handlers/messages/3rdparty.go (1)
12-12: LGTM! Comprehensive JWT authentication and granular permission scopes well-implemented.The integration demonstrates excellent separation of concerns with distinct scopes:
ScopeListfor GET /messagesScopeSendfor POST /messagesScopeReadfor GET /messages/:idScopeExportfor POST /messages/inbox/exportAll handler signatures consistently updated to
users.User, and security annotations properly added to OpenAPI documentation.Also applies to: 17-17, 49-49, 59-59, 66-66, 181-181, 193-193, 198-198, 219-219, 226-226, 231-231, 249-249, 257-257, 262-262, 285-289
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
7-7: LGTM! Authentication middleware successfully refactored for JWT integration.The changes are well-structured and address previous issues:
NewBasicnow usesusers.Serviceand grantsScopeAllfor basic authSetUserprovides consistent user storage across middlewaresGetUsercorrectly returns*users.Userwith nil-safe type assertionWithUserconsistently dereferences the pointer for handlers- Type mismatch issue from previous review is resolved
The integration properly supports both basic auth (with full scopes) and code-based auth flows.
Also applies to: 9-9, 16-59, 82-82, 88-90, 96-96, 102-108, 130-130, 132-132
internal/sms-gateway/modules/auth/service.go (2)
23-53: Constructor wiring and new service dependencies look consistentThe added
usersSvc,devicesSvc, andonlineSvcfields and the updatedNewconstructor parameters are aligned and keep this service focused on orchestration rather than direct data access. No issues noticed here.
83-90: RegisterDevice delegation to devices service looks correctUsing
users.Userand delegating persistence todevicesSvc.Insert(user.ID, &device)cleanly separates concerns between auth and devices. The new signature aligns with the public users service usage.internal/sms-gateway/handlers/3rdparty.go (1)
20-67: Explicit constructor and handler dependencies look goodThe new
thirdPartyHandlerfields andnewThirdPartyHandlersignature (includingusersSvc,jwtSvc,authHandler, logger, and validator) are consistent and make the wiring clearer than fx‑based params. No problems spotted here.
2a6c1d8 to
c56a4ab
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (3)
internal/sms-gateway/handlers/devices/3rdparty.go (1)
87-88: Fix inconsistent route parameter syntax.Line 88 uses
:idwithout a leading slash, which is inconsistent with Fiber routing conventions. Per the past review comment, this should be/:idto match other handlers like webhooks.Apply this diff:
- router.Delete(":id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove)) + router.Delete("/:id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove))internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
124-133: GuardWithUseragainst missing user for clearer failures
WithUsercurrently does:return handler(*GetUser(c), c)If a route is mistakenly wired without an auth middleware that calls
SetUser, this will panic with a nil‑pointer dereference, which is hard to diagnose.Consider adding an explicit nil check and panicking with a clearer message, as previously suggested, e.g.:
func WithUser(handler func(users.User, *fiber.Ctx) error) fiber.Handler { return func(c *fiber.Ctx) error { user := GetUser(c) if user == nil { panic("WithUser called without authenticated user in context") } return handler(*user, c) } }This keeps the contract (“must be used after auth middleware”) but gives a much more actionable failure if it’s violated.
internal/sms-gateway/handlers/mobile.go (1)
56-56: Handle the error fromnanoid.Standard(21).This issue was already flagged in a previous review. The error from
nanoid.Standard(21)should not be silently ignored, as a nil function will cause a panic whenh.idGen()is called.
🧹 Nitpick comments (7)
internal/sms-gateway/users/models.go (1)
13-13: Consider removing commented-out code or adding a TODO.The commented-out
Devicesrelation should either be removed if not needed, or documented with a TODO comment if it's planned for future implementation.Apply this diff if removing is preferred:
ID string `gorm:"primaryKey;type:varchar(32)"` PasswordHash string `gorm:"not null;type:varchar(72)"` - // Devices []models.Device `gorm:"-,foreignKey:UserID;constraint:OnDelete:CASCADE"` models.SoftDeletableModelOr add context if keeping:
- // Devices []models.Device `gorm:"-,foreignKey:UserID;constraint:OnDelete:CASCADE"` + // TODO: Add Devices relation once bidirectional relationship is needed + // Devices []models.Device `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`internal/config/module.go (1)
128-134: Remove redundant type conversion.On line 131,
cfg.JWT.TTLis already of typeDuration(which istime.Durationperinternal/config/types.go), so the explicittime.Duration()conversion is redundant.Apply this diff:
fx.Provide(func(cfg Config) jwt.Config { return jwt.Config{ Secret: cfg.JWT.Secret, - TTL: time.Duration(cfg.JWT.TTL), + TTL: cfg.JWT.TTL, Issuer: cfg.JWT.Issuer, } }),internal/sms-gateway/users/module.go (1)
12-31: Users module DI and migration wiring look solid (minor nit on cache key)The
usersFX module is well‑structured: a named logger, a privateloginCachebuilt viacache.Factory, a private repository, and a publicNewService, with errors from cache creation correctly aborting startup. Theinit‑timedb.RegisterMigration(Migrate)also follows the existing migration pattern.If you expect to reuse
"users:login"elsewhere, consider extracting it into aconstto avoid typos and ease future changes; otherwise this inline literal is fine.internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
113-117: Please verify middleware ordering betweenRequireScopeandWithUserRight now the routes are registered as:
router.Get("", permissions.RequireScope(ScopeList), userauth.WithUser(h.get)) router.Post("", permissions.RequireScope(ScopeWrite), userauth.WithUser(h.post)) router.Delete("/:id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.delete))This makes
permissions.RequireScope(...)run beforeuserauth.WithUser. That’s fine ifpermissions.HasScoperelies only on JWT‑attached claims (or other context) that are already set by upstream middleware, but it will fail if scope checks depend onuserauthhaving populated the user first.Please confirm that
HasScopedoes not depend onuserauth.GetUser(c)(or similar). If it does, you’ll want to swap the order:router.Get("", userauth.WithUser(h.get), permissions.RequireScope(ScopeList))(and similarly for POST/DELETE).
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
14-22: Consider extracting the magic number for clarity.The token extraction logic is correct and properly validates the Bearer scheme. However, the magic number
7(length of "Bearer ") could be extracted as a constant for improved readability.+const bearerPrefix = "Bearer " + func NewJWT(jwtSvc jwt.Service, usersSvc *users.Service) fiber.Handler { return func(c *fiber.Ctx) error { token := c.Get("Authorization") - if len(token) <= 7 || !strings.EqualFold(token[:7], "Bearer ") { + if len(token) <= len(bearerPrefix) || !strings.EqualFold(token[:len(bearerPrefix)], bearerPrefix) { return c.Next() } - token = token[7:] + token = token[len(bearerPrefix):]internal/sms-gateway/users/service.go (1)
35-60: Handle user creation races on unique constraint, not justExistspre‑check
Createcurrently does a read (Exists) beforeInsert. Under concurrent creates for the same username, this can still race and let the DB surface a uniqueness error that is returned as a generic “failed to create user” instead ofErrExists.Consider also mapping the repository’s unique‑constraint error from
InserttoErrExists(and/or relying primarily on the DB constraint) so behavior is deterministic under contention.internal/sms-gateway/handlers/mobile.go (1)
131-133: Optional: Document the credential generation logic.The username takes characters 0-5 and the password takes characters 7-20 from the 21-character ID, skipping the character at index 6. While this is likely intentional for visual separation, consider adding a brief comment explaining the rationale for maintainability.
Example:
id := h.idGen() +// Split the 21-char ID: first 6 for username, skip char 6 as separator, remaining 14 for password username = strings.ToUpper(id[:6]) password = strings.ToLower(id[7:])
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (42)
cmd/sms-gateway/main.go(1 hunks)configs/config.example.yml(1 hunks)go.mod(2 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(6 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(3 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(8 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(5 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(1 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/openapi/docs.go(31 hunks)internal/sms-gateway/users/cache.go(1 hunks)internal/sms-gateway/users/domain.go(1 hunks)internal/sms-gateway/users/errors.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)internal/sms-gateway/users/service.go(1 hunks)
💤 Files with no reviewable changes (3)
- internal/sms-gateway/modules/auth/repository.go
- internal/sms-gateway/models/models.go
- internal/sms-gateway/modules/auth/module.go
🚧 Files skipped from review as they are similar to previous changes (12)
- internal/sms-gateway/handlers/settings/permissions.go
- internal/sms-gateway/handlers/logs/permissions.go
- internal/sms-gateway/handlers/thirdparty/permissions.go
- internal/sms-gateway/handlers/messages/permissions.go
- internal/sms-gateway/users/errors.go
- internal/sms-gateway/handlers/logs/3rdparty.go
- internal/sms-gateway/modules/webhooks/models.go
- internal/sms-gateway/users/cache.go
- internal/sms-gateway/users/repository.go
- internal/sms-gateway/models/migration.go
- go.mod
- cmd/sms-gateway/main.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/handlers/thirdparty/module.go
🧬 Code graph analysis (17)
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (3)
internal/sms-gateway/modules/auth/service.go (1)
Service(23-33)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
SetUser(88-90)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
SetScopes(15-17)
internal/config/module.go (2)
internal/config/config.go (1)
JWT(91-95)internal/config/types.go (1)
Duration(11-11)
internal/config/config.go (1)
internal/config/types.go (1)
Duration(11-11)
internal/sms-gateway/handlers/settings/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/settings/permissions.go (2)
ScopeRead(4-4)ScopeWrite(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
internal/sms-gateway/modules/settings/models.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/users/module.go (3)
internal/sms-gateway/cache/factory.go (1)
Factory(16-18)internal/sms-gateway/users/service.go (1)
NewService(21-33)internal/sms-gateway/users/models.go (1)
Migrate(22-27)
internal/sms-gateway/users/service.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/users/errors.go (1)
ErrExists(7-7)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)pkg/cache/errors.go (1)
ErrKeyNotFound(17-17)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
internal/sms-gateway/modules/auth/service.go (1)
Service(23-33)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (2)
SetScopes(15-17)ScopeAll(10-10)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/handlers/module.go (2)
internal/sms-gateway/app.go (1)
Module(35-60)internal/sms-gateway/handlers/thirdparty/module.go (1)
Module(8-16)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)
internal/sms-gateway/modules/auth/service.go (3)
internal/sms-gateway/online/service.go (2)
Service(14-17)New(28-37)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/models/models.go (1)
Device(17-28)
internal/sms-gateway/handlers/webhooks/3rdparty.go (7)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/messages/permissions.go (1)
ScopeList(10-10)internal/sms-gateway/handlers/webhooks/permissions.go (3)
ScopeList(4-4)ScopeWrite(5-5)ScopeDelete(6-6)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(130-134)internal/sms-gateway/handlers/settings/permissions.go (1)
ScopeWrite(5-5)
internal/sms-gateway/modules/messages/service.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/users/models.go (1)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-15)
internal/sms-gateway/handlers/thirdparty/module.go (3)
internal/sms-gateway/app.go (1)
Module(35-60)internal/sms-gateway/handlers/module.go (1)
Module(16-41)internal/sms-gateway/handlers/thirdparty/auth.go (1)
NewAuthHandler(24-35)
internal/sms-gateway/handlers/3rdparty.go (7)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/devices/3rdparty.go (1)
ThirdPartyController(27-31)internal/sms-gateway/handlers/logs/3rdparty.go (1)
ThirdPartyController(21-23)internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
ThirdPartyController(27-31)internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(18-22)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
NewBasic(21-59)UserRequired(114-122)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
NewJWT(14-42)
internal/sms-gateway/handlers/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/auth/service.go (1)
Service(23-33)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
GetUser(102-109)NewBasic(21-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build / Docker image (linux/amd64)
- GitHub Check: Build / Docker image (linux/arm64)
- GitHub Check: Benchmark
- GitHub Check: Analyze (go)
🔇 Additional comments (47)
internal/sms-gateway/users/models.go (1)
22-27: LGTM!The migration function properly wraps errors and provides clear context.
internal/sms-gateway/users/domain.go (1)
1-19: LGTM!Clean domain model with proper separation from the persistence layer. The
newUserconstructor correctly maps fields from theuserModel.internal/sms-gateway/handlers/middlewares/permissions/permissions.go (2)
19-26: LGTM!The scope checking logic correctly handles both exact matches and the wildcard
ScopeAllscope in a single pass.
28-36: LGTM!The middleware correctly enforces scope requirements with a clear error message.
internal/sms-gateway/handlers/thirdparty/auth.go (2)
59-76: LGTM!The token generation handler correctly validates the request, generates the token with specified scopes and TTL, and returns a properly formatted response.
100-120: LGTM!The error handler properly maps JWT service errors to appropriate HTTP status codes with user-friendly messages.
internal/sms-gateway/modules/auth/service.go (3)
23-53: LGTM!The refactoring properly delegates user management to the
users.Service, improving separation of concerns. The explicit constructor signature is clearer than the previous FX-based approach.
83-90: LGTM!The updated
RegisterDevicecorrectly accepts the newusers.Userdomain type and properly delegates device creation to the devices service.
126-138: LGTM!The
AuthorizeUserByCodemethod correctly retrieves and returns the user viausers.Service, with proper error handling.internal/sms-gateway/modules/settings/models.go (1)
7-7: LGTM!The update to use
users.Useris consistent with the broader migration to the new users domain model and properly maintains the foreign key constraint.Also applies to: 15-15
internal/sms-gateway/handlers/module.go (1)
9-9: LGTM!The third-party module is properly wired into the handlers module composition, extending support for JWT authentication endpoints.
Also applies to: 40-40
internal/sms-gateway/app.go (1)
10-60: JWT and users FX modules are wired correctly into the app graphAdding
users.Module()andjwt.Module()to the top-levelModule(and importing their packages) cleanly integrates user management and JWT into the existing FX graph; the order relative to other modules is fine given FX’s dependency-driven resolution, and it also guarantees the users migrationinitruns.internal/sms-gateway/handlers/thirdparty/module.go (1)
1-16: Third‑party handler module wiring looks consistentUsing
logger.WithNamedLogger("3rdparty")to scope logs for this module and exposingNewAuthHandlerviafx.Provideintegrates cleanly with the existinghandlers.Modulecomposition; this matches the established pattern of module-scoped named loggers. Based on learnings, this use of a logger decorator is appropriate.internal/sms-gateway/handlers/devices/permissions.go (1)
1-6: Devices scope constants are clear and consistentThe
ScopeListandScopeDeletevalues follow the same pattern as other handlers’ scope constants and are ready to be consumed by the permissions middleware.internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
33-111: Move tousers.Userand JWT/scopes on webhook endpoints is coherentSwitching the handler signatures to
func (h *ThirdPartyController) get/post/delete(user users.User, c *fiber.Ctx)and usinguser.IDwithwebhooksSvcaligns with the new users domain and theuserauth.WithUsermiddleware. The added@Security JWTAuthplus@Failure 403annotations match the new scope checks and give accurate OpenAPI documentation for JWT‑protected, scope‑gated access.internal/config/config.go (4)
3-3: LGTM!The
timeimport is necessary for the JWT TTL default initialization.
22-22: LGTM!The JWT field is properly integrated into the Config struct with appropriate YAML tags and comments.
131-134: LGTM!The JWT defaults are sensible: 24-hour TTL is reasonable for session tokens, and leaving
Secretempty ensures users must explicitly configure it.
91-95: No action required—JWT secret validation already exists.The JWT
Secretfield is properly validated ininternal/sms-gateway/jwt/config.go. TheConfig.Validate()method checks that the secret is not empty and meets the minimum length requirement (minSecretLength). The concern in the review comment does not apply.Likely an incorrect or invalid review comment.
internal/sms-gateway/handlers/devices/3rdparty.go (3)
9-12: LGTM!The imports correctly reflect the shift to scope-based permissions and the new users domain type.
36-36: LGTM!The JWTAuth security annotations correctly document the new JWT authentication option alongside the existing API authentication.
Also applies to: 60-60
46-46: LGTM!The handler signatures correctly use the new
users.Usertype, maintaining compatibility with the existing logic.Also applies to: 72-72
configs/config.example.yml (2)
41-44: LGTM!The JWT configuration block is well-structured with clear comments. Leaving
secretempty in the example is appropriate, as it forces explicit configuration and the comment clearly indicates the behavior.
50-50: LGTM!The comment updates improve clarity without changing functionality.
Also applies to: 52-53, 55-56
internal/sms-gateway/handlers/webhooks/permissions.go (1)
1-7: LGTM!The permission scope constants follow the established pattern and naming conventions used across other handlers.
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (2)
24-35: LGTM!The token parsing and user fetching logic has proper error handling that correctly distinguishes between authentication failures (401) and server errors (500).
37-41: LGTM!The middleware correctly sets the authenticated user and scopes in the request context, enabling downstream permission checks.
internal/sms-gateway/modules/messages/service.go (3)
15-15: LGTM!The users package import is necessary for the type changes in this file.
124-133: LGTM!The type change to
users.Useraligns with the broader refactor, and the added error wrapping improves error context for debugging.
135-135: LGTM!The signature change to
users.Useris consistent with the broader refactor and maintains compatibility with the existing implementation.internal/config/types.go (5)
11-11: LGTM!The Duration type wrapper enables custom unmarshaling behavior for configuration.
14-19: LGTM!The nil-safe accessor correctly returns a zero duration for nil values.
22-27: LGTM!The String() method properly handles nil values and delegates to the standard library for non-nil cases.
29-36: LGTM!The UnmarshalText implementation correctly parses duration strings and wraps errors for better diagnostics.
38-48: LGTM!The UnmarshalYAML implementation correctly decodes YAML and delegates to UnmarshalText, with proper interface assertions for compile-time verification.
internal/sms-gateway/handlers/settings/3rdparty.go (4)
8-8: LGTM!The imports correctly reflect the shift to scope-based permissions and the new users domain type.
Also applies to: 12-12
39-39: LGTM!The JWTAuth security annotations correctly document JWT authentication support across all endpoints.
Also applies to: 60-60, 95-95
48-48: LGTM!The handler signatures correctly use the new
users.Usertype, maintaining compatibility with the existing logic.Also applies to: 72-72, 107-107
127-129: LGTM!The route registrations properly enforce scope-based permissions with appropriate read/write distinctions.
internal/sms-gateway/handlers/messages/3rdparty.go (1)
12-18: User type migration and scope‑based routing look consistentThe switch to
users.Userin handler signatures, plus wiring routes throughpermissions.RequireScope(...)anduserauth.WithUser, is coherent with the new users/jwt modules:
- All handlers only rely on
user.ID, whichusers.Userexposes.- Per‑route scopes (
ScopeList,ScopeSend,ScopeRead,ScopeExport) match the new permissions middleware contract.- Swagger additions for
JWTAuthand403 Forbiddenalign with the new enforcement.No issues from this file’s changes.
Also applies to: 65-176, 198-214, 231-244, 262-282, 284-290
internal/sms-gateway/openapi/docs.go (1)
21-87: JWT token endpoints and security definitions align with handler changesThe generated OpenAPI now describes:
/3rdparty/v1/auth/tokenPOST and/3rdparty/v1/auth/token/{jti}DELETE with bothApiAuthandJWTAuthsecurity and appropriate 401/403/500 responses.- JWT‑protected 3rd‑party messages/webhooks endpoints with explicit 403 responses.
smsgateway.TokenRequest/TokenResponseschemas and aJWTAuthsecurityDefinition.This matches the new JWT + permissions wiring in the handlers; no additional OpenAPI changes needed here.
Also applies to: 88-128, 330-517, 519-645, 1787-1827, 1904-1912, 1935-1946
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
16-21: Basic/code auth now correctly populate users and scopesThe Basic and Code middlewares now:
- Authenticate via
users.Service.Login/auth.Service.AuthorizeUserByCode.- Store a
users.Uservalue inLocalsviaSetUser.- Expose retrieval through
GetUser/HasUserand, for Basic, grantpermissions.ScopeAll.This fixes the previous pointer/value mismatch and cleanly integrates with the permissions/jwt middlewares.
Also applies to: 49-56, 82-90, 95-107
internal/sms-gateway/handlers/3rdparty.go (1)
8-15: 3rd‑party handler wiring cleanly integrates users, JWT, and auth routesThe updated
thirdPartyHandlercomposition looks sound:
- Injects
usersSvcandjwtSvcand feeds them intouserauth.NewBasicandjwtauth.NewJWT, then enforcesuserauth.UserRequiredfor all 3rd‑party v1 routes registered after theUsecall.- Registers health endpoints before the auth middleware, keeping
/3rdparty/v1/healthas an unauthenticated readiness probe, which matches the OpenAPI.- Wires the new
/authsub‑router throughauthHandler, so token issue/revoke endpoints share the same auth stack.No issues spotted in this wiring.
Also applies to: 20-67, 69-81
internal/sms-gateway/handlers/mobile.go (4)
43-55: LGTM: Clean explicit dependency constructor.The new constructor design with explicit dependencies is clear and maintainable, replacing the fx-based approach effectively. All dependencies are properly initialized and assigned.
Also applies to: 57-74
141-152: LGTM: Device registration flow is correct.The device registration logic properly handles both authenticated and new users. The nil-pointer safety for dereferencing
userat line 141 is guaranteed by the preceding logic, and the response correctly provides credentials only for newly created users.
197-207: LGTM: Clean integration with users service.The changes to
getUserCode,changePassword, and routing properly integrate with the newusers.Service:
- Signature changes align with middleware expectations
- Password change logic correctly delegates to the users service
- Basic auth middleware updated to use the users service
These changes are consistent with the architectural shift to the users subsystem.
Also applies to: 230-236, 241-263
127-129: No action required —user.IDcorrectly represents the username.Verification confirms the code is correct: in
users.Service.Create, the username parameter is explicitly assigned to theIDfield (line 51 of service.go:ID: username). TheUserdomain model stores username asIDby design. The code at lines 127-129 properly extracts this for theLoginresponse field and aligns with the service contract.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
deployments/prometheus/alerts/jwt-alerts.yml (3)
4-12: Investigate potential alert duplication.
JWT_Validation_ErrorRate_High(lines 4–12) andJWT_Validation_Failures_High(lines 34–42) appear to monitor nearly identical conditions: both trigger when the JWT validation error rate exceeds 10% over a 5-minute window. The naming suggests different purposes, but the expressions are functionally equivalent. Clarify whether this duplication is intentional (e.g., for redundancy or future differentiation) or if one should be removed or renamed with a distinct purpose.Also applies to: 34-42
5-5: Standardize division-by-zero guards for consistency.Lines 5 and 35 both protect against division by zero but use different approaches:
clamp_min(..., 1e-9)vs.max(..., 0.00001). For consistency and maintainability, standardize on a single approach. Themax()idiom is more commonly used in Prometheus expressions.Apply this diff to standardize:
- expr: rate(sms_auth_jwt_tokens_validated_total{status="error"}[5m]) / clamp_min(rate(sms_auth_jwt_tokens_validated_total[5m]), 1e-9) > 0.1 + expr: rate(sms_auth_jwt_tokens_validated_total{status="error"}[5m]) / max(rate(sms_auth_jwt_tokens_validated_total[5m]), 0.00001) > 0.1Also applies to: 35-35
24-32: Verify critical severity for revocation alert.Line 28 assigns
severity: criticalto an alert that fires whenever the revocation error rate is> 0(line 25). This means even a single revocation failure within a 5-minute window triggers a critical-severity alert. Consider whether this is appropriate for your operational posture, or if awarningseverity with a higher threshold (e.g., sustained error rate > 0.01/s) would be more suitable. Critical alerts should typically indicate active service degradation or data loss risk.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)pkg/cache/memory.go(13 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/memory.go
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/memory.go
🧬 Code graph analysis (1)
pkg/cache/memory.go (1)
pkg/cache/options.go (1)
AndDelete(205-209)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Analyze (go)
🔇 Additional comments (7)
pkg/cache/memory.go (7)
9-56: LGTM! Well-documented cache structure and constructor.The type definition and constructor are clear and comprehensive. The documentation properly explains the use cases, TTL behavior, and thread-safety guarantees.
58-91: LGTM! Clean item structure with proper expiration logic.The
memoryItemstructure andisExpiredmethod are well-designed. The nil safety check and zero-time handling ensure robust expiration semantics.
93-175: LGTM! Cleanup and drain operations are correctly implemented.The methods properly handle expired items and maintain thread safety. The
Drainimplementation cleverly uses the cleanup callback to atomically copy and clear the cache.Based on learnings, the cleanup method correctly removes expired items before executing the callback, ensuring
Drainreturns only non-expired items.
207-254: LGTM! Get method with proper option handling and fixed TTL logic.The implementation correctly handles:
- Fast path with read lock for simple gets
- Write lock for option-based modifications
- Early return for expired/missing keys
- Various TTL update strategies
The
defaultTTLcase (lines 244-249) now properly checksm.ttl > 0before setting expiration, addressing the bug from the previous review where zero TTL would cause immediate expiration.
256-342: LGTM! Set operations are thread-safe and semantically correct.All three methods properly implement their semantics:
GetAndDeletecorrectly delegates toGetwith theAndDeleteoptionSetunconditionally overwrites with proper lockingSetOrFailatomically checks for non-expired keys and returnsErrKeyExistsappropriately
344-434: LGTM! Helper methods are well-structured and consistent.The internal helpers maintain consistent behavior:
newItemcorrectly applies default TTL only whenm.ttl > 0(lines 359-361)getItemandgetValueproperly distinguish between missing and expired keyscleanupefficiently removes expired entries while maintaining atomicity for the callback
436-453: LGTM! Resource cleanup and interface compliance.The
Closemethod appropriately returns nil for the memory implementation, and the compile-time interface check ensuresMemoryCachecorrectly implements theCacheinterface.
f9de36b to
53baff7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
66-86: NewCode path does not set permission scopes.
NewBasicsetspermissions.SetScopes(c, []string{permissions.ScopeAll})on successful auth, butNewCodeonly stores the user without setting any scopes. This inconsistency means code-based authentication will fail allRequireScopechecks.If code-based auth should grant full access like Basic auth:
SetUser(c, *user) + permissions.SetScopes(c, []string{permissions.ScopeAll}) return c.Next()If code-based auth has limited scopes, explicitly set the appropriate ones.
♻️ Duplicate comments (9)
internal/config/config.go (1)
125-128: Critical: JWT Secret still has no default value or validation.As noted in previous reviews, the JWT initialization leaves
Secretempty, making all tokens insecure. The application should either fail at startup when Secret is not provided or include explicit documentation and validation.Based on past review comments.
internal/sms-gateway/users/cache.go (1)
51-54: Critical: Credential-based cache keys still pose security risks.As noted in previous reviews, using hashed credentials as cache keys creates security vulnerabilities:
- Deterministic hashing without salt enables correlation attacks
- Cache becomes an authentication oracle (cache hit proves valid credentials)
- Password changes won't invalidate old cache entries
- Timing differences could leak credential validity information
Consider using session tokens, opaque identifiers, or adding a server-side salt to the hash function instead.
Based on past review comments.
api/requests.http (1)
200-218: Critical: Verify scope namedevices:writeagainst defined constants.Based on previous review comments, the scope
"devices:write"on line 211 may not match the permission constants defined in the codebase. The devices permissions file defines"devices:list"and"devices:delete". Please verify this scope is valid or update it to match the actual permission constants.Run the following script to verify all defined device permission scopes:
#!/bin/bash # Description: Find device permission scope constants echo "=== Device Permission Scopes ===" rg -n 'Scope.*=.*"devices:' --type go -C 2Based on past review comments.
internal/sms-gateway/jwt/config.go (1)
8-32: JWT config validation is solid; optionally validateIssueras wellThe
SecretandTTLchecks look good (including rejecting non‑positive TTL). If your tokens rely on a non‑emptyissclaim, you may also wantValidateto reject emptyIssuervalues, similar to howSecretis handled; otherwise, consider explicitly documenting thatIssueris optional.internal/sms-gateway/handlers/mobile.go (1)
43-58: Handlenanoid.Standarderror and tighten ID slicing for credentialsTwo related issues in the ID/credential generation path:
nanoid.Standarderror handling
At line 57 the error fromnanoid.Standard(idLength)is ignored. If it fails and returns a nil generator,h.idGen()will panic at runtime whenpostDeviceruns.Credential slicing looks off‑by‑one
InpostDevice,usernameisid[:6]andpasswordisid[7:], so the character at index 6 is unused. WithidLength = 21this still works, but it’s surprising and easy to break ifidLengthchanges.Consider something along these lines:
func newMobileHandler( @@ -) *mobileHandler { - const idLength = 21 - idGen, _ := nanoid.Standard(idLength) +) *mobileHandler { + const idLength = 21 + idGen, err := nanoid.Standard(idLength) + if err != nil { + // Fail fast during startup rather than leaving a nil generator. + panic(fmt.Sprintf("failed to initialize ID generator: %v", err)) + } @@ func (h *mobileHandler) postDevice(c *fiber.Ctx) error { @@ - if authUser := userauth.GetUser(c); authUser != nil { + if authUser := userauth.GetUser(c); authUser != nil { user = authUser username = user.ID } else { id := h.idGen() - username = strings.ToUpper(id[:6]) - password = strings.ToLower(id[7:]) + username = strings.ToUpper(id[:6]) + password = strings.ToLower(id[6:])#!/bin/bash # Grep for other nanoid.Standard usages that may also ignore errors rg -n 'nanoid\.Standard\('Also applies to: 123-139
internal/sms-gateway/users/service.go (1)
59-99: Avoid using plaintext passwords as cache keys and make cache delete best‑effortTwo security/robustness concerns here (previously noted in an earlier review):
Plaintext passwords as cache keys
LoginandChangePasswordpass the rawpassword/currentPasswordinto the cache (Get/Set/Delete). Depending on cache backend and observability, this risks leaking secrets (e.g., via keys in logs, metrics, Redis key inspection). It’s safer for the cache API to never see the raw password at all—e.g., key only byusernameor by a short‑lived opaque login token.Password change fails on cache.Delete error
ChangePasswordreturns immediately ons.cache.Deletefailure, even thoughLogintreats cache errors as non‑fatal. This can block password changes purely due to cache issues.Suggested direction:
- Refactor
loginCacheso its public API doesn’t require the plaintext password (e.g.,Get(ctx, username),Set(ctx, username, user),Delete(ctx, username)), and update the callsites accordingly.- Make cache deletion best‑effort in
ChangePasswordso storage is updated even if cache eviction fails, e.g.:func (s *Service) ChangePassword(ctx context.Context, username, currentPassword, newPassword string) error { @@ - if delErr := s.cache.Delete(ctx, username, currentPassword); delErr != nil { - return delErr - } + if delErr := s.cache.Delete(ctx, username, currentPassword); delErr != nil { + s.logger.Warn("failed to delete login cache on password change", + zap.String("username", username), + zap.Error(delErr), + ) + }#!/bin/bash # Locate loginCache definition to plan a safe API change rg -n 'type loginCache' internal/sms-gateway/users pkg/cacheinternal/sms-gateway/users/repository.go (1)
59-67: CheckRowsAffectedto detect non-existent users.GORM's
Updatedoesn't return an error when zero rows are affected. If the user ID doesn't exist, this method silently succeeds, which is inconsistent withGetByIDreturningErrNotFound.Apply this diff:
func (r *repository) UpdatePassword(id string, passwordHash string) error { - if err := r.db.Model((*userModel)(nil)). + result := r.db.Model((*userModel)(nil)). Where("id = ?", id). - Update("password_hash", passwordHash).Error; err != nil { + Update("password_hash", passwordHash) + if err := result.Error; err != nil { return fmt.Errorf("can't update password: %w", err) } + if result.RowsAffected == 0 { + return ErrNotFound + } return nil }internal/sms-gateway/handlers/messages/3rdparty.go (1)
327-332: Inconsistent route parameter syntax.Line 329 uses
:idwithout a leading slash, while other handlers (e.g., webhooks at line 126) use/:id. This inconsistency could cause routing issues depending on Fiber's path resolution.Apply this diff for consistency:
- router.Get(":id", permissions.RequireScope(ScopeRead), userauth.WithUser(h.get)).Name(route3rdPartyGetMessage) + router.Get("/:id", permissions.RequireScope(ScopeRead), userauth.WithUser(h.get)).Name(route3rdPartyGetMessage)pkg/cache/redis.go (1)
25-52: Fix HTTL return handling in Lua TTL script (HTTL with FIELDS returns an array)When using
HTTL key FIELDS 1 field, Redis returns an array of TTLs; herettlis treated as a scalar (if ttl < 0/ttl + ttlDelta), which will cause a Lua runtime error once this path is exercised, breakingGetcalls that use TTL‑update options.Update the script to read the first element of the returned array before doing arithmetic:
elseif ttlDelta > 0 then - local ttl = redis.call('HTTL', KEYS[1], 'FIELDS', '1', field) - if ttl < 0 then - ttl = 0 - end - local newTtl = ttl + ttlDelta - redis.call('HExpire', KEYS[1], newTtl, 'FIELDS', '1', field) + local ttlArr = redis.call('HTTL', KEYS[1], 'FIELDS', '1', field) + local ttl = ttlArr[1] or 0 + if ttl < 0 then + ttl = 0 + end + local newTtl = ttl + ttlDelta + redis.call('HExpire', KEYS[1], newTtl, 'FIELDS', '1', field) endThis matches the expected HTTL response shape and keeps the existing negative‑TTL handling.
Please double‑check against your target Redis version’s HTTL documentation to confirm the array return shape when using
FIELDS.
🧹 Nitpick comments (9)
internal/sms-gateway/handlers/devices/permissions.go (1)
1-6: Permission scope definitions look good; consider adding GoDoc comments.The scope constants and their values are clear and consistent, and centralizing them in this package is a good approach. Since
ScopeListandScopeDeleteare exported, you may optionally add brief GoDoc comments (e.g.,// ScopeList is the permission required to list devices.) to improve discoverability in IDEs andgodoc.internal/sms-gateway/handlers/base/handler.go (1)
44-58: Consider more specific error messages for validation paths.The error wrapping correctly preserves the error chain. However, both validation paths (Line 47 for
Validator.Varand Line 53 for customValidate()) use the identical message "failed to validate". More specific messages would improve debugging.Apply this diff for clearer error messages:
if h.Validator != nil { if err := h.Validator.Var(out, "required,dive"); err != nil { - return fmt.Errorf("failed to validate: %w", err) + return fmt.Errorf("failed to validate struct: %w", err) } } if req, ok := out.(Validatable); ok { if err := req.Validate(); err != nil { - return fmt.Errorf("failed to validate: %w", err) + return fmt.Errorf("failed to validate business rules: %w", err) } }internal/sms-gateway/handlers/logs/permissions.go (1)
3-5: Consider adding documentation for the scope constant.The constant definition is correct and follows a clear naming pattern. However, adding a godoc comment would help developers understand its purpose and usage within the permissions middleware.
Apply this diff to add documentation:
+// ScopeRead defines the permission scope required to read logs. const ( ScopeRead = "logs:read" )internal/config/types.go (1)
11-48: Duration wrapper and unmarshalling look correct; consider minor API polishThe nil-safe accessors and YAML/Text unmarshalling are implemented cleanly and should behave as expected. Optionally, you could:
- Add a doc comment on the exported
Durationtype itself for lint friendliness.- Add
MarshalText/MarshalYAMLif you later need symmetric serialization.internal/sms-gateway/handlers/settings/3rdparty.go (1)
8-13: Handlers correctly migrated tousers.Userand scope‑guarded; minor redundancy onlyThe migration from
models.Usertousers.Userand the addition ofpermissions.RequireScope+JWTAuthannotations look coherent and match the new auth model. One small thing you might revisit later: bothputandpatchcallBodyParserValidatorand thenc.BodyParser(&settings)on the same body, which is safe but slightly redundant—reusing the validated DTO to build the map, or validating the map directly, could simplify this path.Also applies to: 47-67, 82-101, 117-140
internal/sms-gateway/users/service.go (1)
21-33: Consider validating dependencies inNewService
NewServiceassumesusers,cache, andloggerare non‑nil. If any of these are ever mis‑wired (tests, future module changes), you’ll get a panic on first use rather than a clear initialization error. It’s worth either:
- Adding nil checks and panicking/logging with a clear message, or
- Returning
(*Service, error)and failing early at wiring time.pkg/cache/options.go (1)
11-21: Cache option plumbing and docs look consistent; ensure backends honor new GetOptionsThe expanded documentation for
WithTTL/WithValidUntiland the newGetOption/getOptionshelpers (AndSetTTL,AndUpdateTTL,AndSetValidUntil,AndDefaultTTL,AndDelete) are internally consistent and keep the behavior clear. Just make sure all cache backends (memory, Redis, typed wrappers) interpret the newgetOptionsfields (setTTL,updateTTL,defaultTTL,delete) in the same way so callers get uniform semantics across implementations.Also applies to: 23-50, 52-74, 76-107, 108-209
internal/sms-gateway/handlers/mobile.go (1)
235-238: Differentiate auth vs internal errors inChangePassword(optional)Currently any error from
usersSvc.ChangePassword(including DB/cache issues) is mapped to 401 Unauthorized. For better client semantics, distinguish bad credentials (401) from internal failures (500). This requires custom error types or checking error types in the handler, since the service layer currently returns untyped errors.internal/sms-gateway/jwt/repository.go (1)
28-36: Consider checkingRowsAffectedwhen revoking tokens.
Revokesilently succeeds if the token doesn't exist or is already revoked (due to therevoked_at is nullcondition). Depending on the use case, callers may want to know if revocation actually occurred.If explicit feedback is needed:
func (r *Repository) Revoke(ctx context.Context, jti, userID string) error { - if err := r.db.WithContext(ctx).Model((*tokenModel)(nil)). + result := r.db.WithContext(ctx).Model((*tokenModel)(nil)). Where("id = ? and user_id = ? and revoked_at is null", jti, userID). - Update("revoked_at", gorm.Expr("NOW()")).Error; err != nil { + Update("revoked_at", gorm.Expr("NOW()")) + if err := result.Error; err != nil { return fmt.Errorf("can't revoke token: %w", err) } + if result.RowsAffected == 0 { + return ErrTokenNotFound // or a specific "already revoked" error + } return nil }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (66)
.golangci.yml(2 hunks)api/mobile.http(1 hunks)api/requests.http(5 hunks)cmd/sms-gateway/main.go(1 hunks)configs/config.example.yml(1 hunks)deployments/grafana/dashboards/jwt.json(1 hunks)deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)go.mod(2 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/base/handler.go(2 hunks)internal/sms-gateway/handlers/base/handler_test.go(9 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(6 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(3 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(8 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(5 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/jwt/config.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/metrics.go(1 hunks)internal/sms-gateway/jwt/models.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/repository.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/cache.go(0 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(2 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/openapi/docs.go(25 hunks)internal/sms-gateway/users/cache.go(1 hunks)internal/sms-gateway/users/domain.go(1 hunks)internal/sms-gateway/users/errors.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)internal/sms-gateway/users/service.go(1 hunks)pkg/cache/cache.go(1 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(13 hunks)pkg/cache/options.go(4 hunks)pkg/cache/redis.go(10 hunks)pkg/cache/typed.go(1 hunks)
💤 Files with no reviewable changes (4)
- internal/sms-gateway/models/models.go
- internal/sms-gateway/modules/auth/repository.go
- internal/sms-gateway/modules/auth/module.go
- internal/sms-gateway/modules/auth/cache.go
✅ Files skipped from review due to trivial changes (1)
- cmd/sms-gateway/main.go
🚧 Files skipped from review as they are similar to previous changes (22)
- internal/sms-gateway/handlers/settings/permissions.go
- internal/sms-gateway/handlers/devices/3rdparty.go
- pkg/cache/errors.go
- internal/sms-gateway/handlers/base/handler_test.go
- .golangci.yml
- internal/sms-gateway/jwt/module.go
- internal/sms-gateway/handlers/webhooks/permissions.go
- internal/sms-gateway/handlers/messages/permissions.go
- go.mod
- internal/sms-gateway/handlers/thirdparty/module.go
- internal/sms-gateway/users/errors.go
- internal/sms-gateway/app.go
- internal/sms-gateway/handlers/logs/3rdparty.go
- internal/config/module.go
- internal/sms-gateway/handlers/module.go
- internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql
- internal/sms-gateway/modules/settings/models.go
- internal/sms-gateway/handlers/thirdparty/auth.go
- internal/sms-gateway/jwt/metrics.go
- pkg/cache/cache.go
- internal/sms-gateway/jwt/jwt.go
- internal/sms-gateway/jwt/models.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/redis.gopkg/cache/memory.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/redis.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/redis.gopkg/cache/memory.go
🧬 Code graph analysis (21)
internal/config/config.go (2)
internal/config/types.go (1)
Duration(11-11)internal/worker/config/types.go (1)
Duration(11-11)
internal/sms-gateway/modules/webhooks/models.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/jwt/errors.go (1)
internal/sms-gateway/jwt/service.go (1)
New(24-51)
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (2)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
SetUser(88-90)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
SetScopes(15-17)
internal/sms-gateway/jwt/config.go (1)
internal/sms-gateway/jwt/errors.go (1)
ErrInvalidConfig(8-8)
internal/sms-gateway/users/cache.go (5)
pkg/cache/typed.go (2)
Typed(89-91)NewTyped(118-122)internal/config/config.go (1)
Cache(78-80)internal/sms-gateway/users/domain.go (1)
User(5-10)pkg/cache/options.go (1)
AndSetTTL(123-127)pkg/cache/errors.go (2)
ErrKeyNotFound(19-19)ErrKeyExpired(36-36)
internal/sms-gateway/modules/messages/service.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/handlers/settings/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/settings/permissions.go (2)
ScopeRead(4-4)ScopeWrite(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (1)
Device(18-29)
internal/sms-gateway/users/repository.go (2)
internal/sms-gateway/users/errors.go (2)
ErrNotFound(6-6)ErrExists(7-7)pkg/mysql/errors.go (1)
IsDuplicateKeyViolation(13-19)
internal/sms-gateway/users/models.go (5)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)internal/sms-gateway/jwt/models.go (1)
Migrate(33-38)internal/sms-gateway/models/migration.go (1)
Migrate(13-18)internal/sms-gateway/modules/settings/models.go (1)
Migrate(28-33)internal/sms-gateway/modules/webhooks/models.go (1)
Migrate(39-44)
pkg/cache/redis.go (1)
pkg/cache/options.go (1)
AndDelete(205-209)
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/webhooks/permissions.go (3)
ScopeList(4-4)ScopeWrite(5-5)ScopeDelete(6-6)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/handlers/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/devices/3rdparty.go (1)
ThirdPartyController(19-23)internal/sms-gateway/handlers/logs/3rdparty.go (1)
ThirdPartyController(21-23)internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(19-23)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
NewJWT(14-42)
internal/sms-gateway/users/service.go (3)
internal/sms-gateway/users/domain.go (1)
User(5-10)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)pkg/cache/errors.go (1)
ErrKeyNotFound(19-19)
internal/sms-gateway/jwt/service.go (6)
internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/repository.go (1)
Repository(10-12)internal/sms-gateway/jwt/metrics.go (3)
Metrics(24-31)StatusError(20-20)StatusSuccess(19-19)internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (4)
ErrInitFailed(7-7)ErrInvalidParams(9-9)ErrInvalidToken(10-10)ErrTokenRevoked(11-11)internal/config/types.go (1)
Duration(11-11)
internal/sms-gateway/users/module.go (3)
internal/sms-gateway/cache/factory.go (1)
Factory(16-18)internal/sms-gateway/users/service.go (1)
NewService(21-33)internal/sms-gateway/users/models.go (1)
Migrate(29-34)
internal/sms-gateway/modules/auth/service.go (4)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/modules/devices/service.go (1)
Service(15-24)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/models/models.go (2)
Device(18-29)NewDevice(31-37)
pkg/cache/memory.go (1)
pkg/cache/options.go (1)
AndDelete(205-209)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (2)
SetScopes(15-17)ScopeAll(10-10)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/handlers/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
NewBasic(21-59)
🪛 Gitleaks (8.29.1)
api/mobile.http
[high] 71-71: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 72-72: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Analyze (go)
🔇 Additional comments (44)
deployments/grafana/dashboards/jwt.json (1)
1-1192: ✅ All previously flagged metric issues have been resolved.The dashboard now correctly references the actual metrics defined in the JWT service:
- Lines 88, 152, 314, 389, 609, 674, 896, 996: All metric names are now correct and consistent with the service implementation (
sms_auth_jwt_tokens_issued_total,sms_auth_jwt_tokens_validated_total,sms_auth_jwt_tokens_revoked_total)- Error metrics: Lines 389 and 996 correctly use the existing counters with
status="error"filters instead of querying non-existent error metrics- Histogram metrics: Latency queries (lines 489, 499, 509, 776, 786, 796, 1096, 1106, 1116) correctly reference histogram buckets for percentile calculations
- Templating variables: Lines 1146 and 1169 properly reference the base metric for dynamic instance/job discovery
The dashboard is well-structured with comprehensive JWT observability coverage: token issuance/validation/revocation rates, success rates, error tracking, and latency distributions across p50/p95/p99 percentiles.
internal/sms-gateway/handlers/base/handler.go (3)
20-26: LGTM! Standard error wrapping improves portability.The shift from
fiber.NewErrorto standard Go error wrapping withfmt.Errorffollows best practices and reduces coupling to the Fiber framework.
28-34: LGTM! Consistent error handling.The error wrapping is consistent with the pattern established in
BodyParserValidator.
36-42: LGTM! Consistent error handling.The error wrapping is consistent with the pattern established in the other parser methods.
internal/sms-gateway/handlers/logs/permissions.go (1)
1-5: Scope naming pattern is consistent across handlers.The
"logs:read"scope follows the established naming convention used throughout handler packages. All scopes follow the"resource:action"format in lowercase:messages:read,messages:send,settings:read,webhooks:list,devices:delete, etc. No inconsistencies found.deployments/prometheus/alerts/jwt-alerts.yml (1)
1-32: LGTM! Previous critical issues have been resolved.All previously identified issues have been addressed:
- Division-by-zero protection is now in place using
max(rate(...), 0.00001)(line 5)- Metric label selectors correctly use
status="error"instead of non-existentreasonlabels- Threshold consistency between expressions and annotations is maintained (10% throughout)
The alert definitions are now correct and will function as intended.
internal/sms-gateway/modules/auth/service.go (2)
35-53: LGTM! Clean refactor to explicit dependency injection.The constructor signature has been properly refactored from an fx Params bundle to explicit arguments, making dependencies clear and testable. The migration from internal models to the public users.Service is architecturally sound.
133-145: LGTM! Previous method naming issue resolved.The method now correctly calls
GetByID(line 139), resolving the previous critical issue whereGetByUsernamewas being called with a user ID parameter. The return type has been properly updated to*users.User.internal/sms-gateway/jwt/disabled.go (1)
8-28: LGTM! Clean no-op implementation for disabled JWT.The disabled service correctly implements the Service interface by returning
ErrDisabledfor all operations. This provides a safe fallback when JWT is not configured, following the Null Object pattern.api/mobile.http (1)
66-73: LGTM! Static analysis warning is a false positive.The static analysis tool flagged these as potential API keys, but these are test credentials in an HTTP request file used for development and testing purposes. No security concerns here.
internal/sms-gateway/modules/webhooks/models.go (1)
8-26: LGTM! Consistent type migration to public users.User.The change from
models.Usertousers.Useraligns with the broader architectural refactor. GORM constraints and relationships are correctly preserved.internal/sms-gateway/models/migration.go (1)
13-18: LGTM! Migration responsibility correctly split.The removal of User from this migration function aligns with the architectural decision to manage user migrations in the dedicated users module. The Device model correctly remains here.
internal/sms-gateway/users/module.go (1)
12-32: LGTM! Well-structured fx module following best practices.The module correctly encapsulates:
- Private dependencies (login cache, repository) to prevent external coupling
- Public service API for consumers
- Proper error handling in the cache factory (line 19)
- Migration registration in init for database schema management
The dependency wiring is clean and follows fx conventions.
internal/sms-gateway/handlers/thirdparty/permissions.go (1)
1-5: LGTM! Permission constant follows established conventions.The new
ScopeTokensManageconstant follows the naming pattern of other scope constants in the codebase and provides appropriate permission control for token management operations.internal/sms-gateway/users/models.go (1)
10-34: LGTM!The user model follows established patterns in the codebase, with appropriate field types and a consistent migration implementation.
configs/config.example.yml (1)
41-44: LGTM! JWT config includes sensible defaults.The configuration properly documents that leaving
secretempty disables JWT functionality, providing a clear opt-in mechanism for JWT authentication.internal/sms-gateway/users/domain.go (1)
5-19: LGTM!The domain model is clean and follows standard patterns. The constructor correctly maps fields from the database model to the domain type.
internal/sms-gateway/modules/messages/service.go (1)
127-175: LGTM!The migration from
models.Usertousers.Useris correctly applied. The new type has the requiredIDfield, and all usages are consistent with the new domain model.internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
14-42: LGTM!The JWT authentication middleware is well-implemented with proper error handling:
- Correctly validates Bearer token format (case-insensitive per RFC 6750)
- Distinguishes between authentication failures (401) and system errors (500)
- Properly sets user and permission scopes in the request context
internal/sms-gateway/jwt/errors.go (1)
5-12: Error set for JWT is clear and consistentThe exported error values cover the main failure modes and are suitable for wrapping and checks; no changes needed.
internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
3-36: Scope middleware logic is solid; Go version confirms compatibilityThe locals keying and
HasScope/RequireScopebehavior (includingScopeAllas wildcard) look good and integrate cleanly with the auth layer. Theslices.ContainsFuncusage is fully supported by your Go 1.24.3 and test toolchain 1.23.2, so no compatibility concerns.internal/sms-gateway/handlers/webhooks/3rdparty.go (3)
8-11: LGTM!Import changes correctly introduce the permissions middleware and users package, aligning with the broader migration from
models.Usertousers.User.
56-63: LGTM!Handler signature correctly updated to use
users.User. The implementation properly accessesuser.IDwhich is available on the newusers.Usertype.
123-127: LGTM!Route registration correctly applies scope-based authorization before user extraction. The middleware chain order (
RequireScope→WithUser) ensures authorization is checked before handlers execute.internal/sms-gateway/users/repository.go (3)
22-31: LGTM!The
Existsmethod correctly uses a COUNT query with proper error wrapping.
33-45: LGTM!The
GetByIDmethod now correctly passesuser(not&user) to GORM'sTake, and properly mapsgorm.ErrRecordNotFoundto the domain-specificErrNotFound.
47-57: LGTM!The
Insertmethod correctly detects duplicate key violations using the MySQL helper and wraps them with the domain-specificErrExistserror.internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
21-58: LGTM!
NewBasiccorrectly authenticates viausersSvc.Login, stores the user viaSetUser, and grants full permissions withScopeAll. The flow is consistent with the JWT-based authorization model.
88-110: LGTM!
SetUserandGetUserare well-implemented. The type assertion correctly expects a value type and returns a pointer for nil-safety.
132-141: LGTM!
WithUserproperly checks for nil user before dereferencing, returningfiber.ErrUnauthorizedinstead of panicking. This addresses the past review feedback.internal/sms-gateway/jwt/repository.go (3)
10-18: LGTM!Repository struct and constructor follow the established pattern.
20-26: LGTM!
Insertcorrectly uses context and wraps errors appropriately.
38-47: LGTM!
IsRevokedcorrectly returnsfalsefor non-existent tokens (count=0), which is appropriate—non-existent tokens should be rejected by signature verification, not revocation checks.internal/sms-gateway/handlers/messages/3rdparty.go (5)
12-16: LGTM!Import changes correctly introduce permissions middleware and users package.
76-173: LGTM!Handler signature updated to
users.User. The implementation correctly usesuser.IDfor device selection and logging.
195-211: LGTM!Handler signature updated correctly. The
userparameter is properly passed toSelectStates.
228-242: LGTM!Handler signature updated correctly. The
userparameter is properly passed toGetState.
260-282: LGTM!Handler signature updated correctly. The
user.IDis properly used for device lookup and logging.internal/sms-gateway/handlers/3rdparty.go (2)
20-67: Constructor & dependency wiring look correctThe new fields (
usersSvc,jwtSvc,authHandler) andnewThirdPartyHandlerwiring correctly initializebase.Handlerand all controllers; no obvious nil or mis‑wiring risks here.
69-81: Auth middleware order correctly supports both Basic and JWTApplying
userauth.NewBasicbeforejwtauth.NewJWT, followed byuserauth.UserRequired(), lets clients authenticate with either Basic (for initial token issuance) or Bearer JWT. Sincejwtauth.NewJWTis a no‑op when the header is notBearer ..., it won't interfere with Basic‑only requests to/auth/token.internal/sms-gateway/jwt/service.go (1)
24-181: JWT service implementation is consistent and defensively validated
New/GenerateToken/ParseToken/RevokeTokenhandle input validation, token issuance, revocation checks, and metrics in a coherent way; capping TTL viamin(ttl, s.config.TTL)and rejecting emptyuserID/scopesavoid malformed tokens. No correctness issues stand out.internal/sms-gateway/openapi/docs.go (1)
21-191: Swagger output now matches JWT auth flow and token APIThe generated definitions for
JWTAuth,/3rdparty/v1/auth/token(withTokenRequest/TokenResponseand seconds‑basedttl),/3rdparty/v1/auth/token/{jti}, and the addedJWTAuthsecurity entries on 3rd‑party routes align with the new handlers and middleware. Changes look correct for clients.pkg/cache/typed.go (1)
29-407: Typed cache generics & pointer instantiation are handled correctlyThe
Typed[T Item]wrapper now instantiatesTvianewItem[T]()before unmarshalling and enforces thatTis a pointer type, which fixes the previous nil‑pointer unmarshalling issue while keeping the API simple. Delegation to the underlyingCacheis clean and consistent across methods.pkg/cache/memory.go (1)
9-450: In‑memory cache TTL semantics and concurrency look solid
MemoryCachecorrectly distinguishes not‑found vs expired entries, applies default TTL only whenttl > 0, and usescleanupplus map swapping to ensureDrainreturns only non‑expired items while remaining data‑race‑free. The Get/Set/SetOrFail paths are consistent with the documented behavior.
53baff7 to
9a89c63
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
66-86: Code-authenticated requests will fail scope checks—NewCode must set appropriate scopes.Unlike
NewBasic, which setspermissions.ScopeAll(line 55),NewCodeonly stores the user without callingpermissions.SetScopes(). Since all protected endpoints usepermissions.RequireScope()middleware, code-authenticated users will have empty scopes and cannot pass these checks. Either setpermissions.ScopeAllhere as well, or set specific scopes based on the authorization code type.pkg/cache/memory.go (1)
177-412: Potential data race onmemoryItem.validUntilinGet/getItem
MemoryCache.Getobtains a*memoryItemunder the mutex, butgetItemlater callsitem.isExpired(time.Now())after the getter returns, i.e., after the lock has been released. In the no‑options path you do:
RLock→ readitem→RUnlockinside the getter, thengetItemrunsitem.isExpired(time.Now())with no lock held.In the options path, the getter holds a write lock while mutating
item.validUntil, then defersUnlockbefore returning;getItemagain callsitem.isExpiredafter the lock is released. If other goroutines concurrently callSet,SetOrFail, orGetwith TTL‑updating options on the same key, you can end up with unsynchronized reads/writes ofmemoryItem.validUntil, which is a concurrency bug under the Go memory model.To make the implementation truly “safe for concurrent use” as documented, consider restructuring so that:
- The expiration check and value read happen while holding either the read or write lock (e.g., move
isExpiredand value extraction into the getter closures), andgetItem/getValueno longer callisExpiredonitemafter the lock has been released, or you treatmemoryItemas immutable and replace map entries instead of mutating them in place.That would eliminate the potential data race while preserving the existing TTL and option semantics.
♻️ Duplicate comments (11)
pkg/cache/errors.go (1)
21-36: Documentation still doesn't clarify backend-specific behavior.The documentation for
ErrKeyExpiredremains generic and doesn't specify that this error is only returned by the memory cache implementation. Based on the retrieved learnings, the Redis implementation uses native field-level TTL and expired fields are automatically removed, meaning Redis returnsErrKeyNotFoundfor both expired and non-existent keys.This is the same issue raised in the previous review, which was marked as addressed but doesn't appear to be reflected in the current code.
internal/sms-gateway/modules/settings/models.go (1)
7-8: DeviceSettings.User now uses users.User – ensure association remains correctAligning
DeviceSettings.Userwith the newusers.Usertype is consistent with the broader refactor. As with Webhook, please verify that GORM treatsusers.Useras the correct associated model (table name, primary key) soforeignKey:UserID;constraint:OnDelete:CASCADEstill works as intended.Same concern as in
modules/webhooks/models.go.Also applies to: 17-18
internal/config/config.go (1)
21-22: Empty default JWT secret is still possible – enforce non‑empty secret at startup
Config.JWT.Secrethas no default andDefault()only setsTTLandIssuer, so ifJWT__SECRET/config is missing the app can run with an empty secret, producing trivially forgeable tokens. Either:
- Fail fast on startup when
cfg.JWT.Secret == ""(e.g., injwt.Configvalidation or module wiring), or- At minimum, document here that
Secretmust be explicitly configured and is never allowed to be empty.This was already raised on a previous revision and still appears unresolved.
Also applies to: 87-91, 125-128
go.mod (1)
7-7: Avoid merging client-go pseudo‑version to master; jwt/v5 addition is fine
github.com/android-sms-gateway/client-gois still pinned to a pseudo‑version (v1.9.6-0.20251123133512-f7816d96f90a), which is suitable for dev but brittle for master (reproducibility and traceability). Please switch to a tagged release (e.g.,v1.9.6or another stable tag) before merging and regeneratego.sumviago mod tidy.The direct
github.com/golang-jwt/jwt/v5 v5.3.0requirement looks appropriate; coexisting with an indirect v4 is expected given dependencies.Also applies to: 18-18
api/requests.http (1)
200-218: Critical: Invalid scopedevices:writeon line 211.Based on past review comments and the permission constants defined in
internal/sms-gateway/handlers/devices/permissions.go, the scope"devices:write"does not exist. The devices package only defines:
ScopeList = "devices:list"ScopeDelete = "devices:delete"Using an undefined scope will cause authorization failures when this token is used against device endpoints.
Apply this diff to fix the scope name:
"scopes": [ "messages:send", "messages:read", "devices:list", - "devices:write", + "devices:delete", "webhooks:list", "webhooks:write", "settings:read", "settings:write", "logs:read" ]Based on learnings from past review comments.
internal/sms-gateway/handlers/mobile.go (1)
56-58: Handle the error fromnanoid.Standard.The error from
nanoid.Standard(idLength)is silently ignored. If initialization fails and returns a nil function, callingh.idGen()will cause a nil pointer panic.This was flagged in a previous review. Apply the suggested fix to handle the error properly.
internal/sms-gateway/handlers/thirdparty/auth.go (1)
97-102: Validatejtiparameter is non-empty.The handler extracts
jtifrom path parameters but doesn't validate it before passing toRevokeToken. An emptyjtishould be rejected with a 400 Bad Request.This was flagged in a previous review. Apply the suggested fix:
func (h *AuthHandler) deleteToken(user users.User, c *fiber.Ctx) error { jti := c.Params("jti") + if jti == "" { + return fiber.NewError(fiber.StatusBadRequest, "jti parameter is required") + } if err := h.jwtSvc.RevokeToken(c.Context(), user.ID, jti); err != nil {internal/sms-gateway/users/service.go (2)
59-65: Cache keying with raw password is a security concern.The
loginCache.GetandSetmethods receive the plaintext password, which risks leaking secrets through logs, metrics, or external cache systems. Consider keying the cache only on username (or a derived non-secret value).This was flagged in a previous review. The suggested approach is to change the cache API to never include plaintext passwords in keys.
90-92: Make cache deletion best-effort inChangePassword.Returning immediately on
cache.Deleteerror blocks password changes due to cache problems, even thoughLogintreats cache failures as non-fatal. This inconsistency could lock users out of password changes during cache outages.This was flagged in a previous review. Apply the suggested fix:
- if delErr := s.cache.Delete(ctx, username, currentPassword); delErr != nil { - return delErr - } + if delErr := s.cache.Delete(ctx, username, currentPassword); delErr != nil { + s.logger.Warn("failed to delete login cache on password change", zap.String("username", username), zap.Error(delErr)) + }internal/sms-gateway/handlers/messages/3rdparty.go (1)
327-331: Fix inconsistent route parameter syntax.Line 329 uses
:idwithout a leading slash, while the webhooks handler uses/:id. This inconsistency could cause routing issues in Fiber.Apply this diff:
- router.Get(":id", permissions.RequireScope(ScopeRead), userauth.WithUser(h.get)).Name(route3rdPartyGetMessage) + router.Get("/:id", permissions.RequireScope(ScopeRead), userauth.WithUser(h.get)).Name(route3rdPartyGetMessage)internal/sms-gateway/handlers/devices/3rdparty.go (1)
92-95: Fix inconsistent route parameter syntax.Line 94 uses
:idwithout a leading slash, while the webhooks handler uses/:id. This inconsistency could cause routing issues in Fiber.Apply this diff:
- router.Delete(":id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove)) + router.Delete("/:id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove))
🧹 Nitpick comments (13)
internal/sms-gateway/handlers/logs/permissions.go (1)
3-5: Scope constant is clear and idiomatic
ScopeRead = "logs:read"is well‑named, exported appropriately for cross‑package use, and follows a conventionaldomain:actionscope pattern. No changes needed here.If more log‑related scopes are added later, consider grouping them in this file or a shared
scopesmodule for discoverability.deployments/prometheus/alerts/jwt-alerts.yml (1)
24-32: Consider relaxing the > 0 threshold or add context to runbooks.The
JWT_Revocation_Failuresalert fires with critical severity whenever the revocation error rate is non-zero—even a single error in a 5-minute window will trigger. While this may be intentional for such a critical operation, it's extremely sensitive and could lead to alert fatigue if transient revocation failures occur (e.g., brief database unavailability).Consider either:
- Setting a minimum error rate threshold (e.g.,
> 0.001), or- Adding runbook context clarifying that any revocation failure warrants immediate investigation.
pkg/cache/errors.go (1)
53-53: Add documentation for consistency with other errors.The new
ErrFailedToCreateZeroValueerror lacks documentation, which is inconsistent with the comprehensive documentation style established for all other errors in this file. Please add documentation explaining:
- When this error is returned
- What scenario triggers it (e.g., reflection failure, type constraints)
- An example demonstrating its usage
Apply this pattern:
+ // ErrFailedToCreateZeroValue indicates failure to instantiate a zero value. + // + // This error is returned when the cache implementation cannot create a zero + // value instance for the requested type, typically due to reflection constraints + // or invalid type parameters. + // + // Example: + // // Get with invalid type parameter + // _, err := cache.Get[SomeType](ctx, "key") + // if errors.Is(err, cache.ErrFailedToCreateZeroValue) { + // // Handle zero value creation failure + // } ErrFailedToCreateZeroValue = errors.New("failed to create zero item")internal/sms-gateway/handlers/base/handler_test.go (4)
77-113: Revised status expectations for body parsing vs validation look consistent with new error wrappingThe table now distinguishes:
- Fiber’s own BodyParser error on missing body → 422 (via wrapped fiber.Error).
- All other validation failures (missing name, age too low) → 500.
This aligns with the handler change to wrap only the underlying error and let the global error handler derive the status. From an API semantics perspective, these are user-input problems that could reasonably be 4xx; if the intent is to treat them as internal errors, this is fine, but it’s worth double‑checking with the API contract/clients before locking it in.
152-182: Query validation tests now model all failures as internal errorsAll invalid query cases (non‑integer age, missing name/age, age too low) now expect 500. This matches the implementation where:
- Parse failures from
QueryParserare generic errors, and- Validator/custom validation errors are wrapped without an HTTP status and thus become 500.
If you eventually want to surface these as 400/422 to clients, consider introducing a typed “validation error” that the global error handler can map to a 4xx, instead of relying purely on generic errors.
214-238: Params parser invalid ID case correctly aligned with new internal-error semanticsTreating the
invalid IDcase as a 500 now matchesValidateStructreturning a wrapped generic error (no embedded fiber.Error). As with the queries, if route params failing business validation should be reported as 4xx, you may later want a dedicated validation error type for these instead of a plain 500.
272-333: ValidateStruct tests correctly reflect new error contract but only distinguish OK vs InternalServerErrorThe updated table and assertion:
- Treat all “invalid” scenarios as
StatusInternalServerError.- Only check two modes: “no error” vs “some error” (implicitly 500 via the global handler).
This is consistent with the implementation, which no longer returns framework‑typed errors for validation but just wraps them. If you later add more granular error classes (e.g., typed validation errors), you may want to extend these tests to assert those types rather than just mapping everything invalid → 500.
internal/sms-gateway/handlers/base/handler.go (1)
20-54: Switch to fmt-wrapped errors is correct and preserves underlying causes; consider typed validation errorsThe new pattern:
BodyParserValidator,QueryParserValidator, andParamsParserValidatornow usefmt.Errorf("failed to parse …: %w", err), andValidateStructwraps both validator andValidatableerrors withfmt.Errorf("failed to validate: %w", err),is sound:
- It removes the tight coupling to Fiber’s
fiber.NewErrorwhile still allowing the global error handler to recover HTTP codes from embedded*fiber.Errorviaerrors.As.- It adds useful context while preserving the original error for logging and classification.
Given the updated tests, all non‑Fiber parse/validation errors now effectively become 500. If you’d like more precise API semantics (e.g., 400/422 on invalid input) without reintroducing framework types here, you could introduce your own
ParseError/ValidationErrortypes and have the central error middleware map those to appropriate 4xx codes.internal/sms-gateway/modules/webhooks/models.go (1)
8-9: Switching Webhook.User to users.User improves domain consistency—verify GORM mappingUsing
users.Userfor theUserassociation (withforeignKey:UserID;constraint:OnDelete:CASCADE) aligns this module with the new users domain, which is good. Please just confirm that:
users.Useris configured so GORM maps it to the sameuserstable (e.g., viaTableName()or consistent naming), and- Preload/association queries on
Webhook.Userstill behave correctly with soft‑delete semantics you expect.Assuming those hold, this change looks solid.
Also applies to: 24-25
internal/sms-gateway/handlers/mobile.go (1)
284-285: Address the authentication protection TODO.The comment indicates this endpoint should use
userauth.NewBasicprotection instead ofdeviceauth. If this is intentional technical debt, consider creating an issue to track it.Would you like me to help refactor this endpoint to use proper user authentication, or open an issue to track this for a future PR?
internal/sms-gateway/handlers/devices/3rdparty.go (1)
39-61: Consider adding 403 Forbidden response to OpenAPI annotations.The handler now uses
permissions.RequireScope(ScopeList)which returns 403 on insufficient permissions, but the OpenAPI annotations don't document this response. Other endpoints in this PR (messages, webhooks) include@Failure 403.Add the 403 response annotation:
// @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized" +// @Failure 403 {object} smsgateway.ErrorResponse "Forbidden" // @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"internal/sms-gateway/handlers/3rdparty.go (1)
35-67: Consider builder pattern for constructor with many parameters.The constructor accepts 11 parameters, which can be error-prone during wiring. While acceptable for this explicit DI migration, consider refactoring to a builder or options pattern in a future iteration to improve maintainability.
Example builder pattern:
type ThirdPartyHandlerBuilder struct { usersSvc *users.Service jwtSvc jwt.Service // ... other fields } func NewThirdPartyHandlerBuilder() *ThirdPartyHandlerBuilder { return &ThirdPartyHandlerBuilder{} } func (b *ThirdPartyHandlerBuilder) WithUsersSvc(svc *users.Service) *ThirdPartyHandlerBuilder { b.usersSvc = svc return b } // ... other With methods func (b *ThirdPartyHandlerBuilder) Build() *thirdPartyHandler { // validation and construction }pkg/cache/options.go (1)
5-209: GetOption plumbing is sound; consider clarifying zero/negative TTL semantics forAndSetTTLThe
Option/GetOptionplumbing and helpers (WithTTL,WithValidUntil,AndSetTTL,AndUpdateTTL,AndSetValidUntil,AndDefaultTTL,AndDelete) are coherent and match howMemoryCache.GetusesgetOptions. One minor point:WithTTLexplicitly documentsttl == 0as “no expiry” andttl < 0as “expires immediately”, butAndSetTTLdoesn’t specify how zero/negative durations are treated and the memory backend currently interprets them as “expire attime.Now().Add(ttl)”. It might be worth either aligning the behavior withWithTTLor at least documenting the intended semantics forAndSetTTLto avoid surprises.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (66)
.golangci.yml(2 hunks)api/mobile.http(1 hunks)api/requests.http(5 hunks)cmd/sms-gateway/main.go(1 hunks)configs/config.example.yml(1 hunks)deployments/grafana/dashboards/jwt.json(1 hunks)deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)go.mod(2 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/base/handler.go(2 hunks)internal/sms-gateway/handlers/base/handler_test.go(9 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(6 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(3 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(8 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(5 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/jwt/config.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/metrics.go(1 hunks)internal/sms-gateway/jwt/models.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/repository.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/cache.go(0 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(2 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/openapi/docs.go(25 hunks)internal/sms-gateway/users/cache.go(1 hunks)internal/sms-gateway/users/domain.go(1 hunks)internal/sms-gateway/users/errors.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)internal/sms-gateway/users/service.go(1 hunks)pkg/cache/cache.go(1 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(13 hunks)pkg/cache/options.go(4 hunks)pkg/cache/redis.go(10 hunks)pkg/cache/typed.go(1 hunks)
💤 Files with no reviewable changes (4)
- internal/sms-gateway/models/models.go
- internal/sms-gateway/modules/auth/module.go
- internal/sms-gateway/modules/auth/repository.go
- internal/sms-gateway/modules/auth/cache.go
🚧 Files skipped from review as they are similar to previous changes (25)
- internal/sms-gateway/handlers/module.go
- internal/sms-gateway/handlers/webhooks/permissions.go
- deployments/grafana/dashboards/jwt.json
- internal/sms-gateway/handlers/thirdparty/module.go
- internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql
- internal/sms-gateway/modules/messages/service.go
- internal/sms-gateway/jwt/disabled.go
- internal/sms-gateway/users/module.go
- internal/sms-gateway/handlers/thirdparty/permissions.go
- internal/sms-gateway/handlers/messages/permissions.go
- internal/config/types.go
- internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go
- internal/sms-gateway/handlers/logs/3rdparty.go
- internal/sms-gateway/users/cache.go
- api/mobile.http
- internal/sms-gateway/jwt/config.go
- .golangci.yml
- internal/sms-gateway/jwt/models.go
- pkg/cache/redis.go
- internal/sms-gateway/models/migration.go
- internal/sms-gateway/jwt/repository.go
- internal/sms-gateway/jwt/errors.go
- internal/sms-gateway/handlers/settings/permissions.go
- cmd/sms-gateway/main.go
- internal/sms-gateway/users/repository.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/cache.gopkg/cache/errors.gopkg/cache/memory.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/cache.gopkg/cache/memory.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/errors.go
🧬 Code graph analysis (19)
internal/config/config.go (2)
internal/config/types.go (1)
Duration(11-11)internal/worker/config/types.go (1)
Duration(11-11)
internal/sms-gateway/handlers/settings/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/settings/permissions.go (2)
ScopeRead(4-4)ScopeWrite(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
pkg/cache/cache.go (1)
pkg/cache/options.go (2)
Option(9-9)GetOption(90-90)
internal/sms-gateway/jwt/module.go (5)
internal/sms-gateway/jwt/repository.go (2)
NewRepository(14-18)Repository(10-12)internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)internal/sms-gateway/jwt/service.go (1)
New(24-51)internal/sms-gateway/jwt/models.go (1)
Migrate(33-38)
internal/sms-gateway/handlers/messages/3rdparty.go (11)
internal/sms-gateway/handlers/devices/3rdparty.go (1)
ThirdPartyController(19-23)internal/sms-gateway/handlers/logs/3rdparty.go (1)
ThirdPartyController(21-23)internal/sms-gateway/handlers/settings/3rdparty.go (1)
ThirdPartyController(29-34)internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
ThirdPartyController(27-31)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (1)
ScopeList(4-4)internal/sms-gateway/handlers/messages/permissions.go (4)
ScopeList(10-10)ScopeSend(6-6)ScopeRead(8-8)ScopeExport(12-12)internal/sms-gateway/handlers/webhooks/permissions.go (1)
ScopeList(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)internal/sms-gateway/handlers/logs/permissions.go (1)
ScopeRead(4-4)
internal/sms-gateway/modules/settings/models.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/config/module.go (2)
internal/config/config.go (1)
JWT(87-91)internal/config/types.go (1)
Duration(11-11)
internal/sms-gateway/handlers/mobile.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/auth/service.go (1)
Service(23-33)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/events/mobile.go (1)
MobileController(13-17)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
GetUser(103-110)
internal/sms-gateway/handlers/devices/3rdparty.go (6)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/messages/permissions.go (1)
ScopeList(10-10)internal/sms-gateway/handlers/webhooks/permissions.go (2)
ScopeList(4-4)ScopeDelete(6-6)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/modules/webhooks/models.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/handlers/thirdparty/auth.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/thirdparty/permissions.go (1)
ScopeTokensManage(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/jwt/errors.go (3)
ErrInvalidParams(9-9)ErrInitFailed(7-7)ErrDisabled(6-6)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (2)
SetScopes(15-17)ScopeAll(10-10)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/users/models.go (5)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)internal/sms-gateway/jwt/models.go (1)
Migrate(33-38)internal/sms-gateway/models/migration.go (1)
Migrate(13-18)internal/sms-gateway/modules/settings/models.go (1)
Migrate(28-33)internal/sms-gateway/modules/webhooks/models.go (1)
Migrate(39-44)
internal/sms-gateway/handlers/webhooks/3rdparty.go (6)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/messages/permissions.go (1)
ScopeList(10-10)internal/sms-gateway/handlers/webhooks/permissions.go (3)
ScopeList(4-4)ScopeWrite(5-5)ScopeDelete(6-6)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/handlers/3rdparty.go (7)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/logs/3rdparty.go (1)
ThirdPartyController(21-23)internal/sms-gateway/handlers/messages/3rdparty.go (1)
ThirdPartyController(38-43)internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(19-23)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
NewBasic(21-59)UserRequired(116-124)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
NewJWT(14-42)
internal/sms-gateway/app.go (1)
internal/config/module.go (1)
Module(24-144)
pkg/cache/memory.go (1)
pkg/cache/options.go (1)
AndDelete(205-209)
internal/sms-gateway/users/service.go (5)
internal/sms-gateway/modules/messages/service.go (2)
NewService(41-64)Service(27-39)internal/sms-gateway/modules/auth/service.go (1)
Service(23-33)internal/sms-gateway/users/domain.go (1)
User(5-10)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)pkg/cache/errors.go (1)
ErrKeyNotFound(19-19)
internal/sms-gateway/modules/auth/service.go (5)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/modules/devices/service.go (1)
Service(15-24)internal/sms-gateway/cache/factory.go (1)
Cache(14-14)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/models/models.go (2)
Device(18-29)NewDevice(31-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Analyze (go)
🔇 Additional comments (36)
deployments/prometheus/alerts/jwt-alerts.yml (2)
4-12: LGTM! Division-by-zero protection and labels are correct.The alert properly guards the division with
max(..., 0.00001), uses the validstatus="error"label (not a non-existentreasonlabel), and the threshold (10%) in the expression matches the annotation. This alert follows PromQL best practices and addresses the previous critical issues.
14-22: LGTM! Histogram quantile and latency thresholds are correct.The p99 latency calculation using
histogram_quantile(0.99, ...)is properly constructed, and the threshold (0.5s) is consistent between expression and annotation. The alert correctly targets issuance latency degradation.pkg/cache/errors.go (2)
9-18: Clear and comprehensive documentation.The documentation for
ErrKeyNotFoundclearly explains when this error is returned and provides a practical example.
38-51: Excellent documentation for atomic operation semantics.The documentation for
ErrKeyExistsclearly explains its use case for preventing race conditions and provides a practical example demonstrating conflict handling.internal/sms-gateway/users/models.go (1)
10-34: User ORM model and migration look consistent with existing patternsThe
userModeldefinition,TableName, andMigrateimplementation align with how other modules (tokens, webhooks, settings) structure GORM models and migrations; no issues spotted.internal/sms-gateway/users/errors.go (1)
5-8: Error sentinel definitions are clear and idiomatic
ErrNotFoundandErrExistsgive a simple, shared contract for repository/service callers; implementation is fine as is.internal/sms-gateway/modules/auth/service.go (2)
13-16: Auth service wiring and typed codes cache look soundInjecting
*users.Service,*devices.Service, andonline.ServiceviaNewand storingcodesCacheas*cache.Cache[string]is coherent with the rest of the module graph; initialization of all fields inNewis complete and consistent.Also applies to: 23-33, 35-53
85-96: Using domainusers.Userin auth flows aligns interfaces and storage
RegisterDevicenow taking ausers.User(and passinguser.IDintodevicesSvc.Insert) plusAuthorizeUserByCodereturning*users.Userand callingusersSvc.GetByIDmatches the new users domain/service API and fixes the earlier GetByUsername/ID mismatch. Behavior and error wrapping look correct.Also applies to: 133-145
internal/sms-gateway/app.go (1)
10-11: App module wiring for users and jwt modules looks correctAdding
users.Module()andjwt.Module()into the mainModule()composition (with config/db/etc. already present) cleanly brings the new domains into the app graph; import placement and fx wiring look consistent with existing modules.Also applies to: 24-25, 35-61
internal/config/module.go (1)
136-142: LGTM!The JWT configuration provider correctly maps the config fields to jwt.Config, following the same pattern as other configuration providers in this module.
internal/sms-gateway/jwt/module.go (2)
9-22: LGTM!The conditional JWT service initialization is well-designed: when
config.Secretis empty, the module provides a disabled service stub, allowing the JWT functionality to be gracefully turned off via configuration. This aligns with the config comment inconfigs/config.example.ymlthat says "leave empty to disable JWT functionality."
24-27: LGTM!The migration registration follows the established pattern for database migrations in this codebase.
internal/sms-gateway/handlers/devices/permissions.go (1)
1-6: LGTM!The permission scope constants are clearly defined and follow a consistent naming pattern with other permission scopes in the codebase.
configs/config.example.yml (1)
41-44: LGTM!The JWT configuration block is well-documented with clear comments explaining each field and its corresponding environment variable. The inline comment correctly notes that leaving
secretempty disables JWT functionality, which aligns with the conditional logic ininternal/sms-gateway/jwt/module.go.internal/sms-gateway/users/domain.go (1)
1-19: LGTM!The User domain model is clean and straightforward. The unexported
newUserconstructor provides controlled instantiation from the internaluserModeltype, which is a good encapsulation practice.internal/sms-gateway/jwt/jwt.go (1)
1-27: LGTM!The JWT service interface and associated types are well-designed:
- The
Serviceinterface provides a clean contract for token operationsClaimscorrectly embedsjwt.RegisteredClaimsand adds custom fields with appropriate JSON tagsTokenInfoprovides a clear structure for token generation resultsinternal/sms-gateway/handlers/settings/3rdparty.go (2)
59-66: LGTM!The migration from
models.Usertousers.Useris clean and the handler logic remains unchanged.
137-141: LGTM!The route registrations now correctly enforce scope-based permissions:
- GET requires
ScopeRead- PATCH and PUT require
ScopeWriteThis aligns well with the permission constants defined in
internal/sms-gateway/handlers/settings/permissions.goand follows the same pattern used in other 3rdparty handlers.internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
1-36: Clean permissions middleware implementation.The scope-based access control is well-structured. The use of
slices.ContainsFuncfor checkingScopeAllas a wildcard is a good pattern. The middleware correctly returns 403 Forbidden when the required scope is missing.internal/sms-gateway/handlers/thirdparty/auth.go (1)
107-130: Well-structured error handler.The error translation logic properly maps JWT-specific errors to appropriate HTTP status codes. Using 501 Not Implemented for a disabled token service is a good choice, as it signals the feature isn't available rather than a client error.
internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
123-127: Clean integration of permissions middleware.The route registration properly chains
permissions.RequireScopebeforeuserauth.WithUser, ensuring scope checks occur before user extraction. The handler signature updates frommodels.Usertousers.Userare consistent with the domain refactor.internal/sms-gateway/users/service.go (1)
35-48: User creation implementation is correct.The
Createmethod properly hashes the password before storage and correctly propagates errors with context. The use ofcrypto.MakeBCryptHashfollows security best practices.internal/sms-gateway/handlers/messages/3rdparty.go (1)
56-76: LGTM!The handler signature correctly uses
users.User, and the OpenAPI annotations properly document bothApiAuthandJWTAuthsecurity schemes with the new 403 Forbidden response.internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
88-110: LGTM!
SetUserstores a value andGetUserretrieves a value with proper type assertion. The nil check and pointer return is clean and consistent. This resolves the previous issue about code-based auth not populatingGetUsercorrectly.
126-141: LGTM!
WithUsernow properly handles the nil user case by returningfiber.ErrUnauthorizedinstead of panicking. The implementation is clean and safe.internal/sms-gateway/jwt/service.go (3)
53-117: LGTM - validation and token generation logic is well-structured.The input validation (empty userID, empty scopes, negative TTL) is comprehensive. The TTL capping with
min(ttl, s.config.TTL)enforces a server-side maximum, which is appropriate for security. The token persistence before returning ensures tokens are tracked for revocation.
119-166: LGTM - secure token parsing with proper validation.The implementation correctly:
- Validates signing method (HS256 only)
- Requires expiration claim
- Validates issuer
- Checks revocation status after signature validation
168-182: LGTM!The revocation implementation is straightforward and properly delegates to the repository while tracking metrics.
internal/sms-gateway/openapi/docs.go (3)
21-137: LGTM - new auth/token endpoints properly documented.The token generation and revocation endpoints are well-documented with:
- Proper security requirements (ApiAuth and JWTAuth)
- Request/response schemas
- Error responses including 403 Forbidden
- The
jtipath parameter is now properly defined (line 104-111)
1916-1921: LGTM!The JWTAuth security definition correctly uses
apiKeytype with theAuthorizationheader, which is the standard approach for Bearer token authentication in Swagger 2.0 (which doesn't have native bearer auth support).
1796-1836: LGTM!The token request/response definitions are well-structured:
scopesis required withminItems: 1validationttlis correctly typed as integer (seconds), resolving the previous issue abouttime.Duration- Response includes all necessary fields (
id,access_token,expires_at,token_type)internal/sms-gateway/handlers/3rdparty.go (2)
23-24: LGTM! JWT and users services properly integrated.The new fields correctly support the authentication flow:
usersSvcfor basic auth and user management,jwtSvcfor token operations, andauthHandlerfor auth routes.Also applies to: 32-32
75-76: Authentication middleware chain correctly configured.The middleware ordering is appropriate: basic auth runs first, then JWT auth, then user requirement check. Both auth middlewares use the
usersSvcconsistently. The auth handler registration at/authproperly exposes token endpoints.Also applies to: 80-80
internal/sms-gateway/jwt/metrics.go (1)
98-117: TheObserveIssuance,ObserveValidation, andObserveRevocationmethods withfunc()signature are working as intended. The service code uses closure variable capture to propagate errors: variables are declared before the observation call, assigned within the closure, and checked after it returns. This is a standard Go pattern that maintains proper error handling without requiring the methods to return errors themselves.pkg/cache/cache.go (1)
1-283: Cache interface and package-level docs look consistent with implementationsThe expanded documentation for
Cache(TTL behavior,GetOptionusage,Drainreturning only non‑expired items, and cleanup semantics) aligns with the in‑memory behavior inMemoryCacheand the Redis TTL behavior described in the prior learnings. I don’t see any inconsistencies or API contract problems introduced here.Based on learnings, the
Drain+ expiration wording matches both memory and Redis backends.pkg/cache/typed.go (1)
9-407: Typed cache wrapper correctly handles pointer-typed items and enforcesItemcontractsThe
Iteminterface plusTyped[T Item]wrapper look solid:Set/SetOrFailmarshal before delegating, andGet,GetAndDelete, andDrainnow usenewItem[T]to instantiate a non‑nilTand unmarshal into it. The reflect-basednewItemcorrectly enforces thatTis a pointer type and returns a clear error if misused, addressing the earlier pointer‑unmarshal bug from the past review. I don’t see any functional or concurrency issues here.
0c99081 to
bb23989
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/sms-gateway/handlers/3rdparty.go (1)
85-86: Overdue TODO: Deprecated route/deviceshould be removed.The TODO indicates this route should have been removed after 2025-07-11. Since the current date is December 2025, this cleanup is approximately 5 months overdue.
Would you like me to open an issue to track the removal of this deprecated route?
♻️ Duplicate comments (10)
internal/sms-gateway/users/cache.go (1)
51-54: Critical: Credential-based cache keys pose security risks.This issue was previously flagged. The current implementation still uses deterministic SHA-256 hashing of credentials without salt, creating risks including cache timing oracle attacks, credential correlation, and invalidation challenges when passwords change.
internal/config/config.go (1)
125-128: Empty Secret field remains a security concern.The JWT configuration in Default() leaves the Secret field empty. This issue was previously flagged in earlier review comments. Ensure that startup validation is implemented to prevent the application from running with an empty JWT secret.
internal/sms-gateway/handlers/thirdparty/auth.go (2)
97-105: Consider validatingjtiparameter is non-empty.While Fiber's routing typically requires a non-empty path segment for
:jti, adding explicit validation provides defense-in-depth and clearer error messages.func (h *AuthHandler) deleteToken(user users.User, c *fiber.Ctx) error { jti := c.Params("jti") + if jti == "" { + return fiber.NewError(fiber.StatusBadRequest, "jti parameter is required") + } if err := h.jwtSvc.RevokeToken(c.Context(), user.ID, jti); err != nil { return fmt.Errorf("failed to revoke token: %w", err) }
66-74: Add TTL bounds validation before conversion to prevent overflow.The conversion
time.Duration(req.TTL)*time.Secondhappens before service validation. Ifreq.TTLis a largeuint64, the cast toint64(time.Duration) can overflow, producing incorrect token lifetimes. Thenolint:goseccomment is misleading since overflow occurs in the handler, not the service.Apply this diff to add bounds checking:
+const maxTTLSeconds = 365 * 24 * 60 * 60 // 1 year in seconds + func (h *AuthHandler) postToken(user users.User, c *fiber.Ctx) error { req := new(smsgateway.TokenRequest) if err := h.BodyParserValidator(c, req); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } + if req.TTL > maxTTLSeconds { + return fiber.NewError(fiber.StatusBadRequest, "ttl exceeds maximum allowed value") + } + token, err := h.jwtSvc.GenerateToken( c.Context(), user.ID, req.Scopes, - time.Duration(req.TTL)*time.Second, //nolint:gosec // validated in the service + time.Duration(req.TTL)*time.Second, )internal/sms-gateway/handlers/devices/3rdparty.go (1)
94-97: Fix inconsistent route parameter syntax.Line 96 uses
:idwithout a leading slash, while other handlers (e.g., webhooks) use/:id. For consistency and to follow Fiber conventions when building routes inside a group, add the leading slash.func (h *ThirdPartyController) Register(router fiber.Router) { router.Get("", permissions.RequireScope(ScopeList), userauth.WithUser(h.get)) - router.Delete(":id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove)) + router.Delete("/:id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove)) }internal/sms-gateway/handlers/messages/3rdparty.go (1)
327-331: Inconsistent route parameter syntax.Line 329 uses
:idwithout a leading slash, while line 331 usesinbox/export(also no leading slash). Fiber typically expects paths like/:id. Verify this is consistent with the rest of the codebase or fix to include the leading slash.#!/bin/bash # Check route path conventions in other handlers rg -n --type=go 'router\.(Get|Post|Put|Delete)\s*\(' internal/sms-gateway/handlers/ | head -30internal/sms-gateway/users/repository.go (2)
15-20: EnsurenewRepositoryis provided via the users module / DI containerPreviously, linters reported
repositoryand its methods as unused because the users module didn’t exposenewRepository. With this constructor in place, please confirm thatusers/module.go(or equivalent) now registers it with your DI container (e.g.,fx.Provide(newRepository, fx.Private)or similar) so the service layer can actually receive and use the repository.You can verify usage with:
#!/bin/bash # Check where newRepository and repository are referenced rg -nP 'newRepository\s*\(' internal/sms-gateway -g'*.go' rg -nP '\*repository' internal/sms-gateway -g'*.go'
59-66: Handle “no rows updated” in UpdatePassword to report missing users
UpdatePasswordtreats a successful UPDATE of zero rows the same as a real update. If the user ID doesn’t exist, callers getnilinstead ofErrNotFound, which is inconsistent withGetByID.Consider updating it to check
RowsAffected:func (r *repository) UpdatePassword(id string, passwordHash string) error { - if err := r.db.Model((*userModel)(nil)). - Where("id = ?", id). - Update("password_hash", passwordHash).Error; err != nil { - return fmt.Errorf("can't update password: %w", err) - } + result := r.db.Model((*userModel)(nil)). + Where("id = ?", id). + Update("password_hash", passwordHash) + + if err := result.Error; err != nil { + return fmt.Errorf("can't update password: %w", err) + } + + if result.RowsAffected == 0 { + return ErrNotFound + } return nil }Please verify this matches the expected semantics for your callers (e.g., users service methods that change passwords).
internal/sms-gateway/handlers/mobile.go (2)
235-237: Differentiate auth failures from internal errors inchangePasswordAll
usersSvc.ChangePassworderrors are returned as 401 Unauthorized. That conflates invalid current password with internal issues (cache/DB/hash failures), making debugging harder and misrepresenting server faults to clients. Consider:
- Mapping “invalid current password” (whatever sentinel/type
usersSvcexposes) to 401.- Mapping all other errors to 500 with a generic message, while logging full details.
if err := h.usersSvc.ChangePassword(c.Context(), device.UserID, req.CurrentPassword, req.NewPassword); err != nil { h.Logger.Error("failed to change password", zap.Error(err)) // Pseudocode – use the actual sentinel/type from usersSvc: // if errors.Is(err, users.ErrInvalidPassword) { // return fiber.NewError(fiber.StatusUnauthorized, "invalid current password") // } return fiber.NewError(fiber.StatusInternalServerError, "failed to change password") }
56-57: Handlenanoid.Standarderror to avoid nilidGenpanicsThe error from
nanoid.Standard(idLength)is still ignored; if initialization fails,idGencan be nil andh.idGen()(e.g., Line 134) will panic at runtime. Initialize defensively and fail fast during handler construction.- const idLength = 21 - idGen, _ := nanoid.Standard(idLength) + const idLength = 21 + idGen, err := nanoid.Standard(idLength) + if err != nil { + // Fail fast during startup rather than creating a handler with a nil idGen. + panic(fmt.Sprintf("failed to initialize ID generator: %v", err)) + }
🧹 Nitpick comments (4)
internal/sms-gateway/users/cache.go (1)
56-67: Consider distinguishing cache misses from storage errors.The current error handling treats cache misses (
ErrKeyNotFound,ErrKeyExpired) the same as storage failures. While functionally correct, callers may want to handle expected cache misses differently from unexpected storage errors for logging or metrics purposes.Example refinement:
func (c *loginCache) Get(ctx context.Context, username, password string) (*User, error) { user, err := c.storage.Get(ctx, c.makeKey(username, password), cache.AndSetTTL(loginCacheTTL)) if err != nil { + if errors.Is(err, cache.ErrKeyNotFound) || errors.Is(err, cache.ErrKeyExpired) { + return nil, err // expected cache miss + } return nil, fmt.Errorf("failed to get user from cache: %w", err) } return &User{ ID: user.ID, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, }, nil }internal/sms-gateway/handlers/base/handler.go (2)
20-26: Confirm how wrapped parser errors are translated into HTTP responsesSwitching from
fiber.NewError(...)tofmt.Errorf("failed to parse ...: %w", err)drops the explicitfiber.Error+ status in favor of a generic wrappederror. That’s fine as long as your central error handler (or middleware) now recognizes these parse failures and still maps them to the appropriate 4xx responses rather than defaulting to 5xx.Please double‑check:
- That
BodyParserValidator,QueryParserValidator, andParamsParserValidatorerrors are still treated as client input errors (likely 400) in the new error pipeline.- Whether you want a dedicated sentinel/type (e.g.,
ErrBadRequest) or error wrapper to make this classification more robust than relying on error messages.Also applies to: 28-34, 36-42
44-55: Clarify classification of validation failures and consider adding structured contextBoth the go‑playground validator failure and the
Validatable.Validate()failure are now surfaced asfmt.Errorf("failed to validate: %w", err). That preserves the root error but removes any prior explicit HTTP status or error typing.Two follow‑ups worth confirming:
- That your global error handling maps these validation errors to the intended HTTP status (usually 400), rather than treating them as generic 500s.
- Whether you want to attach additional context (e.g., a typed validation error or field list) here, or at least log via
Handler.Loggerto aid debugging..golangci.yml (1)
255-256: Note: Unrelated lint configuration changes.The modifications to Firebase Messaging and Telebot exclusion patterns appear unrelated to the JWT module introduction. While these changes are low-risk, consider whether they should be in a separate commit for clarity.
Also applies to: 276-278
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (48)
.golangci.yml(2 hunks)cmd/sms-gateway/main.go(1 hunks)configs/config.example.yml(1 hunks)go.mod(2 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/base/handler.go(2 hunks)internal/sms-gateway/handlers/base/handler_test.go(9 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(4 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(5 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(5 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/jwt/metrics.go(1 hunks)internal/sms-gateway/jwt/models.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/cache.go(0 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(2 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/openapi/docs.go(31 hunks)internal/sms-gateway/users/cache.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)internal/sms-gateway/users/service.go(1 hunks)
💤 Files with no reviewable changes (4)
- internal/sms-gateway/modules/auth/repository.go
- internal/sms-gateway/modules/auth/module.go
- internal/sms-gateway/models/models.go
- internal/sms-gateway/modules/auth/cache.go
🚧 Files skipped from review as they are similar to previous changes (18)
- internal/sms-gateway/app.go
- internal/sms-gateway/handlers/logs/permissions.go
- cmd/sms-gateway/main.go
- internal/sms-gateway/modules/messages/service.go
- internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go
- internal/sms-gateway/handlers/base/handler_test.go
- internal/sms-gateway/handlers/webhooks/permissions.go
- internal/sms-gateway/jwt/models.go
- internal/sms-gateway/users/module.go
- internal/sms-gateway/handlers/settings/permissions.go
- internal/sms-gateway/handlers/thirdparty/permissions.go
- configs/config.example.yml
- internal/config/module.go
- go.mod
- internal/sms-gateway/users/service.go
- internal/sms-gateway/handlers/devices/permissions.go
- internal/config/types.go
- internal/sms-gateway/handlers/webhooks/3rdparty.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T23:45:45.502Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: internal/sms-gateway/online/module.go:14-16
Timestamp: 2025-09-10T23:45:45.502Z
Learning: fx.Decorate in uber-go/fx creates module-scoped decorated dependencies, not global modifications. Using fx.Decorate(func(log *zap.Logger) *zap.Logger { return log.Named("module_name") }) is a correct pattern for creating module-specific loggers that are scoped only to that module's dependency graph.
Applied to files:
internal/sms-gateway/handlers/thirdparty/module.go
🧬 Code graph analysis (17)
internal/sms-gateway/modules/settings/models.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/modules/webhooks/models.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (1)
Device(18-29)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/handlers/logs/3rdparty.go (3)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/users/models.go (5)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)internal/sms-gateway/jwt/models.go (1)
Migrate(33-38)internal/sms-gateway/models/migration.go (1)
Migrate(13-18)internal/sms-gateway/modules/settings/models.go (1)
Migrate(28-33)internal/sms-gateway/modules/webhooks/models.go (1)
Migrate(39-44)
internal/config/config.go (2)
internal/config/types.go (1)
Duration(11-11)internal/worker/config/types.go (1)
Duration(11-11)
internal/sms-gateway/users/cache.go (5)
pkg/cache/typed.go (2)
Typed(89-91)NewTyped(118-122)internal/config/config.go (1)
Cache(78-80)internal/sms-gateway/users/domain.go (1)
User(5-10)pkg/cache/options.go (2)
AndSetTTL(123-127)WithTTL(39-50)pkg/cache/errors.go (2)
ErrKeyNotFound(19-19)ErrKeyExpired(36-36)
internal/sms-gateway/users/repository.go (2)
internal/sms-gateway/users/errors.go (2)
ErrNotFound(6-6)ErrExists(7-7)pkg/mysql/errors.go (1)
IsDuplicateKeyViolation(13-19)
internal/sms-gateway/handlers/module.go (2)
internal/sms-gateway/app.go (1)
Module(35-62)internal/sms-gateway/handlers/thirdparty/module.go (1)
Module(8-16)
internal/sms-gateway/jwt/module.go (5)
internal/sms-gateway/jwt/repository.go (2)
NewRepository(14-18)Repository(10-12)internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)internal/sms-gateway/jwt/service.go (1)
New(24-51)internal/sms-gateway/jwt/models.go (1)
Migrate(33-38)
internal/sms-gateway/handlers/thirdparty/auth.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/jwt/errors.go (3)
ErrInvalidParams(9-9)ErrInitFailed(7-7)ErrDisabled(6-6)
internal/sms-gateway/handlers/settings/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/settings/permissions.go (2)
ScopeRead(4-4)ScopeWrite(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/jwt/service.go (5)
internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/repository.go (1)
Repository(10-12)internal/sms-gateway/jwt/metrics.go (3)
Metrics(24-31)StatusError(20-20)StatusSuccess(19-19)internal/sms-gateway/jwt/jwt.go (2)
TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (4)
ErrInitFailed(7-7)ErrInvalidParams(9-9)ErrInvalidToken(10-10)ErrTokenRevoked(11-11)
internal/sms-gateway/handlers/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
NewBasic(21-59)
internal/sms-gateway/modules/auth/service.go (4)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/modules/devices/service.go (1)
Service(15-24)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/models/models.go (2)
Device(18-29)NewDevice(31-37)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (2)
SetScopes(15-17)ScopeAll(10-10)internal/sms-gateway/users/domain.go (1)
User(5-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (48)
internal/sms-gateway/users/cache.go (1)
83-90: Well-handled idempotent delete.The error handling correctly treats missing or expired keys as successful deletions, making the operation idempotent and avoiding spurious errors for callers.
internal/sms-gateway/handlers/messages/permissions.go (1)
1-13: LGTM!The permission scope constants are well-defined and follow a consistent naming pattern. The documentation is clear and the scope values align with standard resource:action conventions.
internal/config/config.go (3)
3-3: LGTM!The
timeimport is correctly added to support the JWT TTL initialization in the Default() function.
87-91: LGTM!The JWT struct is well-defined with appropriate YAML and environment variable tags. The use of the Duration type for TTL enables proper unmarshalling and nil-safe access.
21-21: LGTM!The JWT field is properly added to the Config struct following the existing structural pattern.
internal/sms-gateway/handlers/module.go (1)
9-9: LGTM!The thirdparty module is properly imported and wired into the handlers module following the established pattern used for other handler modules.
Also applies to: 41-41
internal/sms-gateway/handlers/thirdparty/module.go (1)
1-16: LGTM!The module is properly structured following the fx pattern. The named logger provides module-scoped logging, and the NewAuthHandler is correctly exposed via fx.Provide for dependency injection.
internal/sms-gateway/modules/settings/models.go (1)
7-7: LGTM!The migration from
models.Usertousers.Useraligns with the PR's introduction of a centralized users domain. The foreign key constraint remains properly configured.Also applies to: 17-17
internal/sms-gateway/jwt/module.go (3)
14-20: Verify the disabled JWT service behavior.The module returns a disabled JWT service when
Secretis empty rather than failing at startup. While this provides flexibility for development environments, ensure this behavior is intentional and documented. In production, running with JWT disabled could be a security risk if the application expects JWT authentication to be enforced.Consider:
- Documenting when JWT can be safely disabled
- Adding startup warnings when JWT is disabled
- Ensuring protected endpoints properly handle the disabled state
13-13: LGTM!The private providers properly encapsulate the Metrics and Repository dependencies within the JWT module, preventing unintended external usage.
24-27: LGTM!The init function properly registers the JWT migration with appropriate nolint justification for the framework-specific initialization pattern.
.golangci.yml (1)
259-260: LGTM!The exhaustruct exclusions for GoFiber Config and JWT Claims are appropriate additions given the introduction of JWT authentication in this PR. These patterns allow the use of these external types without requiring all fields to be initialized.
internal/sms-gateway/handlers/logs/3rdparty.go (4)
5-5: LGTM!The import changes correctly add the permissions middleware package and migrate to the centralized users package, aligning with the JWT authentication and authorization system introduced in this PR.
Also applies to: 7-7
37-37: LGTM!The OpenAPI documentation correctly reflects the addition of JWT authentication support and the new 403 Forbidden response for insufficient permissions.
Also applies to: 44-44
50-50: LGTM!The method signature correctly migrates to the
users.Usertype, maintaining consistency with the centralized users domain.
58-58: LGTM!The middleware chain correctly enforces scope-based authorization with
permissions.RequireScope(ScopeRead)before user authentication. This ensures that only requests with the appropriate scope can access the logs endpoint.internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
1-36: LGTM!Clean implementation of scope-based permissions middleware. The
HasScopefunction correctly handles missing scopes by returningfalse, and theScopeAllwildcard provides a clean mechanism for granting full access.internal/sms-gateway/handlers/settings/3rdparty.go (1)
140-144: LGTM!Scope-based permissions are correctly applied:
ScopeReadfor GET operations andScopeWritefor mutation operations (PUT/PATCH). The middleware ordering ensures scope verification occurs before user context extraction.internal/sms-gateway/handlers/thirdparty/auth.go (1)
107-129: LGTM on the error handler implementation.The error handler correctly maps JWT-specific errors to appropriate HTTP status codes:
ErrInvalidParams→ 400,ErrInitFailed/ErrInvalidConfig→ 500 with admin contact message,ErrDisabled→ 501. Unknown errors are passed through to Fiber's error handler.internal/sms-gateway/openapi/docs.go (1)
1-6: Generated file correctly reflects JWT integration.The OpenAPI specification now properly documents:
- JWT authentication endpoints (
POST /auth/token,DELETE /auth/token/{jti})JWTAuthsecurity definition alongsideApiAuthfor all 3rdparty endpoints- 403 Forbidden responses for scope-protected endpoints
TokenRequest/TokenResponseschemasSince this is auto-generated, any fixes should be applied to the source handler annotations and regenerated.
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (4)
21-58: LGTM! Well-structured optional Basic auth middleware.The middleware correctly handles the optional authentication pattern: passing through when no Authorization header is present, and authenticating when one is provided. The
SetScopescall withScopeAllappropriately grants full permissions for Basic auth users.
66-86: Consider whether scopes should be set for code-based auth.Unlike
NewBasicwhich setsScopeAll,NewCodedoesn't callSetScopes. If code-based auth should grant specific permissions, add a similarSetScopescall. If this is intentional (e.g., code auth has limited/default permissions managed elsewhere), this is fine as-is.
88-110: LGTM!The
SetUser/GetUserpattern is clean and consistent. Storing a value and returning a pointer for convenience is idiomatic.
132-141: Good improvement: graceful 401 instead of panic.Returning
fiber.ErrUnauthorizedwhen no user is present is safer than the previous panic behavior and provides consistent error handling.internal/sms-gateway/handlers/messages/3rdparty.go (3)
56-73: LGTM! OpenAPI annotations correctly updated for JWT auth.The security annotations now include both
ApiAuthandJWTAuth, and 403 Forbidden response is properly documented.
76-173: LGTM!The handler correctly uses the new
users.Usertype. The user ID references for device lookup and logging are properly updated.
195-282: LGTM!Handler signatures consistently updated to use
users.User. Core logic preserved.internal/sms-gateway/jwt/service.go (4)
24-51: LGTM! Constructor with proper validation.Good defensive programming: validating config, requiring non-nil repository and metrics, and handling nanoid factory creation errors.
53-117: LGTM with minor note on claims redundancy.Input validation is thorough. The TTL capping at
min(ttl, s.config.TTL)enforces a maximum, which is reasonable security policy. Note thatuserIDappears in bothSubjectand customUserIDclaim—this is redundant but harmless if intentional for different consumption patterns.
119-166: LGTM! Secure token parsing with proper validation.Good security practices:
- Restricts valid signing methods to HS256 only via
jwt.WithValidMethods- Requires expiration claim
- Validates issuer
- Checks revocation status after successful parse
168-182: LGTM!Clean revocation implementation with proper metrics tracking.
internal/sms-gateway/modules/auth/service.go (5)
35-53: LGTM! Clean dependency injection.Constructor properly accepts the new
users.Servicedependency. The simplified cache setup (onlycodesCache) reflects the removal of user-related caching from this service.
55-83: LGTM!Secure code generation using
crypto/randwith retry logic for collision handling.
85-96: LGTM!Handler properly adapted to use
users.User. Device creation and insertion logic preserved.
132-145: LGTM!Correctly uses
GetByIDas addressed in previous review. Clean code-to-user resolution flow.
147-165: LGTM!Simplified cleanup focusing only on
codesCache. The unused context parameter incleanis reasonable for interface consistency.internal/sms-gateway/jwt/metrics.go (3)
8-21: LGTM! Clear metric naming conventions.Metric names are descriptive and follow Prometheus conventions. The
nolint:goseccomments are appropriate as these are metric names, not secrets.
33-81: LGTM! Well-structured metrics initialization.Consistent namespace/subsystem across all metrics. Histogram buckets (5ms to 1s) are appropriate for JWT operations. Using
promautoensures proper registration with the default registry.
98-117: LGTM! Clean observer pattern.The
ObserveXmethods correctly usedefer timer.ObserveDuration()to ensure timing is recorded even if the wrapped function panics.internal/sms-gateway/handlers/3rdparty.go (3)
20-33: LGTM!The struct additions are well-organized. Using an interface type for
jwtSvcpromotes testability, while the explicit dependencies are clearly grouped by concern.
35-67: LGTM!Clean transition from DI-based wiring to explicit dependency injection. The constructor properly initializes all fields with passed dependencies.
74-80: LGTM!The middleware chain correctly implements an "either-or" authentication pattern where both Basic auth and JWT auth are attempted, with
UserRequired()ensuring at least one succeeds. Registering the auth handler after the middleware is the correct approach for obtaining JWT tokens via Basic auth.internal/sms-gateway/modules/webhooks/models.go (1)
3-10: Webhook–User relation now targets users.User and looks consistentUpdating the Webhook
Userassociation frommodels.Usertousers.Userwith the sameforeignKey:UserID;constraint:OnDelete:CASCADEkeeps the FK semantics while exposing only the domain‑level user fields (ID/timestamps). Givenusers.Usermaps to theuserstable with a string ID, this change looks correct.Also applies to: 24-26
internal/sms-gateway/users/models.go (1)
10-34: userModel schema and users.Migrate implementation look soundThe unexported
userModel(ID varchar(32), PasswordHash varchar(72), embedded SoftDeletableModel) plusTableName() string { return "users" }matches existing UserID usage and gives a dedicated users‑package migration viaMigrate(db *gorm.DB). The constructornewUserModelkeeps DB concerns encapsulated.internal/sms-gateway/models/migration.go (1)
13-17: Migrations are properly wired via db.RegisterMigration() – no action neededAll models correctly register their Migrate functions through
db.RegisterMigration()in packageinit()functions (users, jwt, webhooks, settings, messages, and models packages). This centralized registration pattern ensures the go-infra-fx framework executes all migrations during application startup in the correct order.internal/sms-gateway/handlers/mobile.go (3)
19-19: usersSvc wiring with handlers and middlewares looks consistentImporting
users, addingusersSvctomobileHandler, threading it throughnewMobileHandler, and using it inuserauth.NewBasic(h.usersSvc)for/deviceand/user/codeis coherent with the userauth middleware design; no issues from a DI or routing standpoint.Also applies to: 32-32, 45-45, 65-65, 247-247, 269-269
201-201: getUserCode signature change aligns with users domainSwitching the parameter type to
users.Usermatches the users service and userauth middleware (SetUser/WithUser) domain model; the rest of the handler body remains valid.
247-247: Basic auth middleware placement for/deviceand/user/codeendpoints looks appropriatePlacing
userauth.NewBasic(h.usersSvc)before the user‑code and server‑key middlewares ensures an already authenticated user is attached to the context early and can short‑circuit server‑key requirements via theNextfunction, which matches the documented behavior for registration and user‑code flows.Also applies to: 269-269
07d779f to
559fade
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/cache/redis.go (1)
420-443: Race condition inSetOrFailbetween HSetNX and HExpireAt.The
SetOrFailmethod usesHSetNXto atomically set the value, then separately callsHExpireAtto set the TTL. If the process crashes or the connection fails between these two operations, the key will exist without a TTL.Consider using a Lua script for atomicity, similar to how
Gethandles TTL updates.// Alternative: Use Lua script for atomic set-if-not-exists with TTL const setOrFailScript = ` local ok = redis.call('HSetNX', KEYS[1], ARGV[1], ARGV[2]) if ok == 1 and tonumber(ARGV[3]) > 0 then redis.call('HExpireAt', KEYS[1], ARGV[3], 'FIELDS', '1', ARGV[1]) end return ok `
♻️ Duplicate comments (11)
pkg/cache/errors.go (1)
22-35: ErrKeyExpired docs still read as backend-agnostic and can mislead users of the Redis cache.The comment and example imply that any
Getwill returnErrKeyExpiredonce TTL elapses, but the Redis backend removes expired fields and returnsErrKeyNotFoundfor both missing and expired keys. This makes the contract as documented here inaccurate for Redis consumers and repeats the earlier concern that was previously fixed.Consider tightening the docs to spell out backend-specific behavior and scope the example to the in-memory cache, for example:
- // This error is returned by Get operations when the requested key exists in the - // cache but its time-to-live (TTL) has elapsed. Expired items may still exist - // in the cache until they are explicitly removed by a Cleanup operation or - // automatically by the cache implementation. + // In the in-memory cache implementation, this error is returned by Get + // operations when the requested key exists in the cache but its time-to-live + // (TTL) has elapsed. Expired items may still exist in memory until they are + // explicitly removed by a Cleanup operation or background sweeper. + // + // In the Redis cache implementation, expired hash fields are removed by Redis + // itself, so Get returns ErrKeyNotFound for both missing and expired keys. @@ - // Example: - // // Set a value with 1 second TTL + // Example (memory cache implementation): + // // Set a value with 1 second TTLThis keeps the high-level semantics while making backend differences explicit to callers.
Based on learnings, the Redis implementation currently returns
ErrKeyNotFoundfor expired keys rather thanErrKeyExpired.internal/sms-gateway/handlers/thirdparty/auth.go (2)
66-74: Add TTL bounds validation before conversion to prevent overflow.The conversion
time.Duration(req.TTL)*time.Secondcan overflow ifreq.TTL(uint64) exceedsmath.MaxInt64. The service validation occurs after this conversion, so overflow happens before the service can validate it.Validate
req.TTLin the handler against a reasonable maximum (e.g., 31,536,000 seconds = 1 year) before converting totime.Duration.Apply this diff to add validation:
+ const maxTTL = 31536000 // 1 year in seconds + if req.TTL > maxTTL { + return fiber.NewError(fiber.StatusBadRequest, "TTL exceeds maximum allowed value") + } + token, err := h.jwtSvc.GenerateToken( c.Context(), user.ID, req.Scopes, - time.Duration(req.TTL)*time.Second, //nolint:gosec // validated in the service + time.Duration(req.TTL)*time.Second, )
97-105: Validate thatjtiparameter is non-empty.The handler extracts
jtifrom path parameters but doesn't validate it before passing tojwtSvc.RevokeToken. An emptyjtishould be rejected with a 400 Bad Request at the handler level.Apply this diff to add validation:
func (h *AuthHandler) deleteToken(user users.User, c *fiber.Ctx) error { jti := c.Params("jti") + if jti == "" { + return fiber.NewError(fiber.StatusBadRequest, "jti parameter is required") + } if err := h.jwtSvc.RevokeToken(c.Context(), user.ID, jti); err != nil { return fmt.Errorf("failed to revoke token: %w", err) }internal/sms-gateway/handlers/devices/3rdparty.go (1)
94-97: Fix route parameter syntax to include leading slash.Line 96 uses
:idwithout a leading slash, which is inconsistent with Fiber routing conventions and the webhooks handler pattern (/:id). This should be/:idfor consistency.Apply this diff:
func (h *ThirdPartyController) Register(router fiber.Router) { router.Get("", permissions.RequireScope(ScopeList), userauth.WithUser(h.get)) - router.Delete(":id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove)) + router.Delete("/:id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove)) }internal/sms-gateway/handlers/messages/3rdparty.go (1)
327-331: Fix inconsistent route parameter syntax.Line 329 uses
:idwithout a leading slash, while other handlers use/:id. This inconsistency could cause routing issues in Fiber.This was already flagged in a previous review. Apply the suggested fix:
- router.Get(":id", permissions.RequireScope(ScopeRead), userauth.WithUser(h.get)).Name(route3rdPartyGetMessage) + router.Get("/:id", permissions.RequireScope(ScopeRead), userauth.WithUser(h.get)).Name(route3rdPartyGetMessage)internal/sms-gateway/users/service.go (1)
99-101: Cache deletion should be best-effort, not blocking.
ChangePasswordreturns immediately ifcache.Deletefails, even thoughLogintreats cache failures as non-fatal. This inconsistency can block password changes due to transient cache issues.Make cache deletion best-effort:
- if delErr := s.cache.Delete(ctx, username, currentPassword); delErr != nil { - return delErr - } + if delErr := s.cache.Delete(ctx, username, currentPassword); delErr != nil { + s.logger.Warn("failed to delete login cache on password change", zap.String("username", username), zap.Error(delErr)) + }This aligns with the resilient error handling pattern used in
Login(lines 72-74, 86-88).internal/sms-gateway/handlers/mobile.go (3)
56-57: Handle the error fromnanoid.Standard.The error from
nanoid.Standard(idLength)is silently ignored. If initialization fails and returns a nil function, callingh.idGen()will cause a nil pointer panic.Apply this diff:
- idGen, _ := nanoid.Standard(idLength) + idGen, err := nanoid.Standard(idLength) + if err != nil { + panic(fmt.Sprintf("failed to initialize ID generator: %v", err)) + }
138-146: Map errors tofiber.NewErrorfor consistency.Lines 140 and 146 wrap
usersSvc.CreateandauthSvc.RegisterDevicefailures withfmt.Errorf. This differs from the pattern used elsewhere in this file (e.g., lines 184, 205, 237) where similar operational failures usefiber.NewError(fiber.StatusInternalServerError, "..."). For consistency and proper error handling through Fiber's error handler, map these tofiber.NewErrorwhile logging the wrapped error separately.+ user, err = h.usersSvc.Create(username, password) if err != nil { - return fmt.Errorf("failed to create user: %w", err) + h.Logger.Error("failed to create user", zap.Error(err)) + return fiber.NewError(fiber.StatusInternalServerError, "failed to create user") } } device, err := h.authSvc.RegisterDevice(*user, req.Name, req.PushToken) if err != nil { - return fmt.Errorf("failed to register device: %w", err) + h.Logger.Error("failed to register device", zap.Error(err)) + return fiber.NewError(fiber.StatusInternalServerError, "failed to register device") }
130-142: Verify the user flow for existing vs. new users.The logic correctly branches: authenticated users reuse their
userauth.GetUser(c)result, while new users get a freshly generated username/password viausersSvc.Create. Note that for existing users,passwordremains empty and will be included in the response (line 154). Per the docstring at line 100 ("Returns user credentials only for new users"), consider omitting the password field for existing users by addingomitemptyto thePasswordJSON tag inMobileRegisterResponse.pkg/cache/redis.go (2)
78-99: Add Redis version requirement to documentation.The implementation uses Redis 7.4.0+ commands (HEXPIREAT, HTTL, HEXPIRE with FIELDS parameter). This minimum version requirement should be documented to prevent runtime errors with older Redis instances.
// RedisCache implements the Cache interface using Redis as the backend. // +// Requires Redis 7.4.0 or later for per-field hash TTL support. +// // This implementation stores all data in a Redis hash, with each cache item
40-48: Fix HTTL array indexing in Lua script.The
HTTLcommand returns an array of TTL values, but lines 43-47 treat the result as a scalar. Line 43 assigns the array directly tottl, and line 47 attempts arithmetic (ttl + ttlDelta), which will fail at runtime since arrays cannot be compared or added.Extract the first element from the array: change line 43 to
local ttl = redis.call('HTTL', KEYS[1], 'FIELDS', '1', field)[1].
🧹 Nitpick comments (3)
pkg/cache/errors.go (1)
53-53: Add a doc comment and align the error message for ErrFailedToCreateZeroValue.
ErrFailedToCreateZeroValueis exported but lacks a GoDoc comment, and the message says “zero item” while the name says “ZeroValue”. To keep linters happy and make usage clearer, consider:- ErrFailedToCreateZeroValue = errors.New("failed to create zero item") + // ErrFailedToCreateZeroValue indicates that the cache failed to construct a + // zero value for the requested type (for example, when creating a default + // value on a cache miss in typed APIs). + ErrFailedToCreateZeroValue = errors.New("failed to create zero value")This documents when callers should expect the error and keeps the message consistent with the identifier.
internal/sms-gateway/jwt/metrics.go (1)
98-117: Timer observation pattern has a subtle issue.The
Observe*methods usedefer timer.ObserveDuration()before callingf(), which works correctly. However, iff()panics, the duration will still be observed (which is typically desired), but the metric won't reflect whether the operation succeeded or failed. Consider whether the caller should handle success/failure metric increments.If you want the observe methods to also track success/failure, consider returning an error from the callback:
func (m *Metrics) ObserveIssuance(f func() error) error { timer := prometheus.NewTimer(m.issuanceDurationHistogram) defer timer.ObserveDuration() return f() }This allows callers to chain success/error metric increments based on the result.
internal/sms-gateway/modules/auth/service.go (1)
120-125: Background goroutine forSetOnlineis reasonable but consider error handling.The goroutine with a 5-second timeout for updating online status is appropriate for fire-and-forget semantics. However, errors from
SetOnlineare silently ignored. Consider logging failures for debugging purposes.go func(id string) { const timeout = 5 * time.Second ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - s.onlineSvc.SetOnline(ctx, id) + if err := s.onlineSvc.SetOnline(ctx, id); err != nil { + s.logger.Debug("failed to set device online", zap.String("device_id", id), zap.Error(err)) + } }(device.ID)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (66)
.golangci.yml(2 hunks)api/mobile.http(1 hunks)api/requests.http(5 hunks)cmd/sms-gateway/main.go(1 hunks)configs/config.example.yml(1 hunks)deployments/grafana/dashboards/jwt.json(1 hunks)deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)go.mod(2 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/base/handler.go(2 hunks)internal/sms-gateway/handlers/base/handler_test.go(9 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(4 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(5 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(5 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/jwt/config.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/metrics.go(1 hunks)internal/sms-gateway/jwt/models.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/repository.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/cache.go(0 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(2 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/openapi/docs.go(31 hunks)internal/sms-gateway/users/cache.go(1 hunks)internal/sms-gateway/users/domain.go(1 hunks)internal/sms-gateway/users/errors.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)internal/sms-gateway/users/service.go(1 hunks)pkg/cache/cache.go(1 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(13 hunks)pkg/cache/options.go(4 hunks)pkg/cache/redis.go(10 hunks)pkg/cache/typed.go(1 hunks)
💤 Files with no reviewable changes (4)
- internal/sms-gateway/models/models.go
- internal/sms-gateway/modules/auth/repository.go
- internal/sms-gateway/modules/auth/cache.go
- internal/sms-gateway/modules/auth/module.go
✅ Files skipped from review due to trivial changes (1)
- cmd/sms-gateway/main.go
🚧 Files skipped from review as they are similar to previous changes (25)
- internal/sms-gateway/handlers/messages/permissions.go
- internal/sms-gateway/handlers/thirdparty/module.go
- internal/sms-gateway/users/cache.go
- internal/config/types.go
- deployments/grafana/dashboards/jwt.json
- internal/sms-gateway/jwt/config.go
- internal/sms-gateway/handlers/settings/permissions.go
- internal/sms-gateway/users/errors.go
- api/requests.http
- internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql
- internal/sms-gateway/handlers/base/handler_test.go
- go.mod
- internal/sms-gateway/handlers/thirdparty/permissions.go
- internal/sms-gateway/modules/webhooks/models.go
- pkg/cache/cache.go
- internal/sms-gateway/handlers/devices/permissions.go
- internal/sms-gateway/users/repository.go
- internal/sms-gateway/handlers/base/handler.go
- internal/sms-gateway/jwt/repository.go
- internal/sms-gateway/jwt/errors.go
- configs/config.example.yml
- internal/sms-gateway/handlers/middlewares/permissions/permissions.go
- internal/sms-gateway/app.go
- internal/sms-gateway/jwt/models.go
- deployments/prometheus/alerts/jwt-alerts.yml
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/errors.gopkg/cache/redis.gopkg/cache/memory.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/errors.gopkg/cache/redis.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/redis.gopkg/cache/memory.go
🧬 Code graph analysis (24)
internal/sms-gateway/jwt/disabled.go (2)
internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (1)
ErrDisabled(6-6)
internal/sms-gateway/users/models.go (5)
internal/sms-gateway/models/models.go (1)
SoftDeletableModel(12-16)internal/sms-gateway/jwt/models.go (1)
Migrate(33-38)internal/sms-gateway/models/migration.go (1)
Migrate(13-18)internal/sms-gateway/modules/settings/models.go (1)
Migrate(28-33)internal/sms-gateway/modules/webhooks/models.go (1)
Migrate(39-44)
internal/sms-gateway/users/module.go (2)
internal/sms-gateway/cache/factory.go (1)
Factory(16-18)internal/sms-gateway/users/models.go (1)
Migrate(29-34)
internal/sms-gateway/handlers/thirdparty/auth.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/jwt/errors.go (3)
ErrInvalidParams(9-9)ErrInitFailed(7-7)ErrDisabled(6-6)
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (4)
internal/sms-gateway/modules/auth/service.go (1)
Service(23-33)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
SetUser(88-90)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
SetScopes(15-17)
pkg/cache/typed.go (3)
pkg/cache/cache.go (1)
Cache(112-283)pkg/cache/options.go (3)
Option(9-9)GetOption(90-90)AndDelete(205-209)pkg/cache/errors.go (1)
ErrFailedToCreateZeroValue(53-53)
internal/sms-gateway/modules/messages/service.go (3)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/modules/messages/domain.go (1)
MessageStateOut(38-44)
internal/config/module.go (2)
internal/config/config.go (2)
Config(12-22)JWT(87-91)internal/config/types.go (1)
Duration(11-11)
internal/config/config.go (2)
internal/config/types.go (1)
Duration(11-11)internal/worker/config/types.go (1)
Duration(11-11)
internal/sms-gateway/handlers/messages/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/messages/permissions.go (4)
ScopeList(10-10)ScopeSend(6-6)ScopeRead(8-8)ScopeExport(12-12)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/handlers/settings/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/settings/permissions.go (2)
ScopeRead(4-4)ScopeWrite(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/jwt/service.go (5)
internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/repository.go (1)
Repository(10-12)internal/sms-gateway/jwt/metrics.go (3)
Metrics(24-31)StatusError(20-20)StatusSuccess(19-19)internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (4)
ErrInitFailed(7-7)ErrInvalidParams(9-9)ErrInvalidToken(10-10)ErrTokenRevoked(11-11)
pkg/cache/redis.go (1)
pkg/cache/options.go (1)
AndDelete(205-209)
internal/sms-gateway/handlers/logs/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/logs/permissions.go (1)
ScopeRead(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/modules/settings/models.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/users/service.go (5)
internal/sms-gateway/modules/auth/service.go (1)
Service(23-33)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/users/errors.go (1)
ErrExists(7-7)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)pkg/cache/errors.go (1)
ErrKeyNotFound(19-19)
internal/sms-gateway/modules/auth/service.go (4)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/modules/devices/service.go (1)
Service(15-24)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/models/models.go (2)
Device(18-29)NewDevice(31-37)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (1)
Device(18-29)
internal/sms-gateway/handlers/webhooks/3rdparty.go (6)
internal/sms-gateway/handlers/logs/3rdparty.go (1)
ThirdPartyController(21-23)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/webhooks/permissions.go (3)
ScopeList(4-4)ScopeWrite(5-5)ScopeDelete(6-6)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/handlers/mobile.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/modules/auth/service.go (1)
Service(23-33)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/events/mobile.go (1)
MobileController(13-17)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
GetUser(103-110)NewBasic(21-59)
internal/sms-gateway/handlers/module.go (1)
internal/sms-gateway/handlers/thirdparty/module.go (1)
Module(8-16)
pkg/cache/memory.go (1)
pkg/cache/options.go (1)
AndDelete(205-209)
internal/sms-gateway/jwt/module.go (5)
internal/sms-gateway/jwt/repository.go (2)
NewRepository(14-18)Repository(10-12)internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)internal/sms-gateway/jwt/service.go (1)
New(24-51)internal/sms-gateway/jwt/models.go (1)
Migrate(33-38)
🪛 Gitleaks (8.30.0)
api/mobile.http
[high] 71-71: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 72-72: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: Analyze (go)
🔇 Additional comments (63)
.golangci.yml (1)
255-260: Configuration changes properly exclude external library types from exhaustruct checks.The added exclusions for Firebase Messaging, GoFiber Config, JWT Claims, and Telebot types follow the established pattern and are appropriate. These third-party libraries use optional field initialization patterns that conflict with exhaustruct's strict checking model.
The regex patterns are well-formed and maintain consistency with existing exclusions in the file. The JWT Claims exclusion (Line 260) aligns with the PR's JWT subsystem introduction.
Also applies to: 276-278
pkg/cache/errors.go (2)
9-18: Expanded ErrKeyNotFound docs look good and align with cache semantics.The description and example for
ErrKeyNotFoundare clear and match typical behavior for both memory and Redis backends (missing or explicitly deleted keys, andGetAndDeleteon non-existent keys). Nothing to change here.
39-50: ErrKeyExists documentation is clear and helpful.The description and example for
ErrKeyExistsdo a good job explaining atomic “create if not exists” usage and concurrent scenarios; no changes needed.internal/sms-gateway/handlers/module.go (1)
9-9: LGTM!The thirdparty module integration into the handlers DI graph is straightforward and correct.
Also applies to: 41-41
api/mobile.http (1)
71-72: Static analysis false positive.The flagged values are test credentials in a development HTTP request file, not actual secrets. No action needed.
internal/sms-gateway/jwt/disabled.go (1)
8-28: LGTM!The disabled service implementation correctly provides a fail-safe JWT service when JWT is not configured. All methods consistently return
ErrDisabled, and the implementation properly satisfies the Service interface.internal/sms-gateway/users/models.go (2)
10-34: LGTM!The user model follows the established pattern in the codebase, with appropriate field types and migration function. The
varchar(72)for PasswordHash is suitable for bcrypt hashes.
13-13: The ID field uses the username as the identifier, not a UUID or ULID. Thevarchar(32)constraint is appropriate for username storage.Likely an incorrect or invalid review comment.
internal/sms-gateway/jwt/jwt.go (1)
10-27: LGTM!The JWT service interface is well-designed with clean separation of concerns. The
Claimsstruct appropriately includes user identification and authorization scopes, and theTokenInfoprovides essential token metadata for clients.internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
21-58: LGTM!The
NewBasicmiddleware has been correctly updated to use the newusers.Service. The authentication flow properly stores the authenticated user with full permissions.
88-110: LGTM!The
SetUserandGetUserhelpers provide consistent type handling. BothNewBasicandNewCodecorrectly dereference user pointers before storing, andGetUserproperly retrieves the value and returns a pointer.
132-141: LGTM!The
WithUserdecorator now correctly handles the nil case by returning 401 Unauthorized, which is the appropriate response when authentication is missing.internal/sms-gateway/handlers/3rdparty.go (2)
35-67: LGTM!The refactored constructor with explicit dependencies is cleaner and more maintainable than the previous fx-based parameter struct approach.
74-80: LGTM!The middleware chain is correctly ordered:
- Basic authentication (optional)
- JWT authentication (optional)
- User required (ensures at least one auth succeeded)
The auth handler registration under
/authis properly scoped.internal/sms-gateway/models/migration.go (1)
14-14: User migration is properly wired viausers/module.go.The User model migration has been successfully moved to the dedicated users package. It is correctly registered during application initialization through the
users/module.goinit() function, which callsdb.RegisterMigration(Migrate)at package startup—following the same pattern used by other packages in the codebase (jwt, models, messages).internal/config/module.go (1)
136-142: LGTM!The JWT config binding correctly maps all fields from
Config.JWTtojwt.Config, with proper conversion ofDurationtotime.Duration.internal/sms-gateway/handlers/logs/permissions.go (1)
1-5: LGTM!The permission scope constant follows the established pattern and will be used for access control on log endpoints.
internal/sms-gateway/modules/settings/models.go (1)
7-7: LGTM!The refactoring to use
users.Useraligns with the new users domain structure while preserving the foreign key relationship and cascade behavior.Also applies to: 17-17
internal/sms-gateway/jwt/module.go (2)
14-20: LGTM! Good fallback pattern.The conditional Service provider elegantly handles the case where JWT is not configured by returning a disabled service when
Secretis empty, allowing the application to start without JWT in certain deployment modes.
25-27: LGTM!Migration registration follows the established pattern in the codebase.
internal/sms-gateway/users/module.go (2)
16-23: LGTM!The login cache initialization properly handles errors from the factory and wraps them with context.
30-32: LGTM!Migration registration follows the established pattern.
internal/sms-gateway/handlers/logs/3rdparty.go (2)
5-7: LGTM!The updates properly integrate JWT authentication with the logs endpoint, including:
- Type refactoring to use
users.User- API documentation updated with JWTAuth security and 403 response
- Handler signature updated consistently
Also applies to: 37-37, 44-44, 50-50
58-58: The middleware execution order is correct.Both
RequireScopeandWithUserdepend on context set by the parent router's middleware chain (userauth.NewBasic()andjwtauth.NewJWT()), not on each other. By the time the route handlers execute, bothc.Locals("user:scopes")andc.Locals("localsUser")are already populated.RequireScopecan safely execute first to validate scopes beforeWithUserextracts the user.Likely an incorrect or invalid review comment.
internal/sms-gateway/handlers/webhooks/permissions.go (1)
1-7: LGTM!The permission scope constants follow the established naming pattern and align with standard CRUD operations for webhooks.
internal/config/config.go (1)
125-128: Acknowledge past review: disabled service pattern mitigates the concern.The past review flagged the empty
Secretdefault as a critical security issue. This concern is now partially mitigated by the disabled service pattern ininternal/sms-gateway/jwt/module.go(line 15), which returns a disabled JWT service whenSecretis empty, preventing insecure token generation.However, consider adding a startup validation or clear documentation (as suggested in the past review) to make it explicit that JWT functionality will be disabled unless
Secretis configured via environment variable or config file.internal/sms-gateway/users/domain.go (2)
5-10: LGTM!Clean domain model with appropriate field types and visibility.
12-19: LGTM!Clean constructor pattern with appropriate visibility and straightforward field mapping.
internal/sms-gateway/modules/messages/service.go (2)
127-140: LGTM!Clean type migration from
models.Usertousers.User. The usage ofuser.IDremains valid with the new type.
142-175: LGTM!Consistent type migration in
GetStatemethod. Field access patterns remain valid.pkg/cache/options.go (1)
76-209: LGTM!Well-structured functional options for Get operations with comprehensive documentation. The
getOptionshelpers (applyandisEmpty) provide clean option accumulation and optimization.internal/sms-gateway/handlers/thirdparty/auth.go (1)
107-130: LGTM!Clean error mapping with appropriate HTTP status codes for each JWT error type.
internal/sms-gateway/handlers/settings/3rdparty.go (2)
60-138: LGTM!Clean type migration to
users.Userwith consistent usage ofuser.IDacross all handler methods.
140-144: LGTM!Proper scope-based authorization applied to all routes. Read operations require
ScopeRead, write operations requireScopeWrite.internal/sms-gateway/handlers/webhooks/3rdparty.go (2)
56-121: LGTM!Consistent type migration to
users.Userwith proper field usage across all webhook handlers.
123-127: LGTM!Well-structured authorization with appropriate scopes:
ScopeListfor GET,ScopeWritefor POST, andScopeDeletefor DELETE operations.internal/sms-gateway/handlers/devices/3rdparty.go (1)
53-92: LGTM!Consistent type migration to
users.Userwith proper field usage in both device handlers.internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
14-42: The JWT middleware correctly allows pass-through when theAuthorizationheader is missing or malformed. This is intentional design: the route middleware chain includesuserauth.UserRequired()immediately afterjwtauth.NewJWT(), which enforces that a user must be authenticated (via JWT or Basic auth) before reaching handlers. No change needed.internal/sms-gateway/handlers/messages/3rdparty.go (1)
56-76: LGTM on handler method signature and OpenAPI updates.The method signature update to
users.Userand the addition ofJWTAuthsecurity with403 Forbiddenresponse documentation properly align with the new JWT authentication subsystem.internal/sms-gateway/openapi/docs.go (1)
1-6: This is a generated file - changes look consistent.The OpenAPI specification has been regenerated with JWT authentication support. The changes include:
- New
/3rdparty/v1/auth/tokenendpoints for token generation and revocationJWTAuthsecurity definition and requirements across all protected endpoints403 Forbiddenresponse documentation for all secured endpointsTokenRequestandTokenResponseschema definitionsSince this file is auto-generated by swaggo/swag, ensure the source handler annotations are kept in sync for future regeneration.
pkg/cache/typed.go (2)
228-244: LGTM on the Get method implementation.The implementation correctly:
- Retrieves data from storage first
- Creates a new instance via
newItem[T]()to handle pointer types- Unmarshals into the properly instantiated instance
- Returns appropriate errors with context
This addresses the previous concern about pointer-typed cache items failing to unmarshal.
392-407: LGTM on the newItem helper.The reflection-based instantiation correctly enforces that
Tmust be a pointer type and creates a properly initialized instance for unmarshaling. The error handling uses the appropriate sentinel error.internal/sms-gateway/jwt/service.go (3)
53-117: LGTM on GenerateToken implementation.The implementation includes proper validation for:
- Non-empty
userID(lines 63-66)- Non-empty
scopes(lines 68-71)- Non-negative
ttl(lines 73-76)The TTL capping at line 89 using
min(ttl, s.config.TTL)enforces a maximum token lifetime from configuration, which is appropriate security hygiene.
119-166: LGTM on ParseToken implementation.The token validation is thorough:
- Signature verification with secret
- Expiration and issuedAt validation
- Issuer verification
- Allowed signing methods restricted to HS256
- Revocation check after cryptographic validation
168-182: LGTM on RevokeToken implementation.Simple delegation to the repository with proper metrics integration. The revocation operation correctly passes both
jtianduserIDfor authorization verification.internal/sms-gateway/handlers/mobile.go (3)
43-75: LGTM on the constructor refactoring.The transition from an fx-based params struct to explicit constructor arguments improves clarity and makes dependencies explicit. The initialization of embedded
base.Handlerand wiring of services/controllers is correct.
201-212: Signature update and authentication flow look correct.The parameter type change from
models.Usertousers.Useraligns with the new users package. The user code generation logic remains unchanged and properly delegates toauthSvc.GenerateUserCode.
243-272: Middleware wiring correctly usesusersSvc.The switch from
authSvctousersSvcforuserauth.NewBasicat lines 247 and 269 aligns with the refactoring. The middleware chain and route registration look correct.internal/sms-gateway/jwt/metrics.go (2)
1-21: Well-structured metric constants.The metric naming follows Prometheus conventions with clear namespace (
sms) and subsystem (auth). The//nolint:goseccomments are appropriate for false positives on metric names containing "token".
33-81: Metrics initialization looks correct.The use of
promautoensures metrics are automatically registered with the default registry. Histogram buckets (0.005s to 1s) are reasonable for authentication operations. The counter vectors correctly use thestatuslabel.internal/sms-gateway/modules/auth/service.go (4)
35-53: Clean constructor refactoring.The explicit dependency injection pattern is clearer than the previous fx-based approach. The
codesCacheinitialization withcodeTTLis retained correctly.
85-96: Signature change from pointer to value is appropriate.Taking
users.Userby value instead of pointer is reasonable since the struct is small (ID + timestamps). The device creation flow correctly usesuser.IDfor the foreign key.
132-145: User retrieval flow is correct.The
AuthorizeUserByCodemethod correctly retrieves and deletes the code atomically, then fetches the user viausersSvc.GetByUsername. The method naming (GetByUsernametaking auserID) was addressed in previous commits per past review comments.
147-165: Cleanup logic correctly simplified.The
Runandcleanmethods now only operate oncodesCache, which is appropriate since user cache management has moved to the users service.pkg/cache/redis.go (3)
55-76: Good documentation for RedisConfig.The struct documentation clearly explains the configuration options and the fallback behavior for client creation.
136-163: Constructor validation and client ownership tracking are correct.The logic properly validates that either a client or URL is provided, handles URL parsing errors, and tracks
ownedClientfor proper cleanup inClose().
458-466: Close method correctly respects client ownership.The
ownedClientflag ensures that externally provided clients are not closed, which is the expected behavior.pkg/cache/memory.go (6)
9-28: Comprehensive type documentation.The documentation clearly explains the use cases, concurrency guarantees, and trade-offs compared to the Redis implementation.
74-91: Well-documented expiration check.The
isExpiredmethod correctly handles nil safety and zero-valuevalidUntilsemantics (no expiration).
207-254: Get method correctly handles TTL options.The implementation properly upgrades to a write lock when options are provided and handles all TTL modification cases. The
AndDefaultTTLfix (lines 244-249) now correctly checksm.ttl > 0before setting expiration, avoiding immediate expiration when no default TTL is configured.
330-342: SetOrFail correctly allows overwriting expired keys.The check at line 335 properly treats expired keys as non-existent, allowing the set operation to proceed. This is consistent with the documented behavior.
422-434: Cleanup callback pattern is correct.Based on learnings, the cleanup method removes expired items first, then executes the callback while still holding the lock. This ensures methods like
Drainget a consistent view of non-expired items.
161-175: Drain method correctly uses cleanup callback.The implementation atomically drains all non-expired items by leveraging the cleanup callback pattern. The old map is captured and replaced with a new empty map while holding the lock.
559fade to
206ce94
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/sms-gateway/handlers/mobile.go (1)
123-156: Align error handling and verify password field behavior inpostDevice.Two issues to address:
Error handling pattern inconsistency
Lines 140 and 146 usefmt.Errorf(...)directly, but other handlers in this file (patchDeviceat lines 183–184,getUserCodeat lines 204–205,changePasswordat lines 236–237) follow a consistent pattern: log withh.Logger.Error()and returnfiber.NewError(fiber.StatusInternalServerError, ...). The suggested diff alignspostDevicewith the established pattern and prevents internal details from leaking to clients.Password field for existing users
The swagger comment (line 100) correctly states "Returns user credentials only for new users". However, whenauthUser != nil, thepasswordvariable remains empty but is still included inMobileRegisterResponse. Verify the JSON tag onsmsgateway.MobileRegisterResponse.Password—if it lacksomitempty, clients will receive"password": ""for existing users, contradicting the swagger documentation. If needed, either update the client-go type definition or conditionally populate thePasswordfield only for new users.
♻️ Duplicate comments (16)
pkg/cache/errors.go (1)
21-36: Re‑verify ErrKeyExpired semantics across backends and align docsThe comment states that
GetreturnsErrKeyExpiredwhen the key’s TTL has elapsed, but previous implementations had:
- the in‑memory cache returning
ErrKeyExpiredfor expired entries, and- the Redis cache using field‑level TTL and returning
ErrKeyNotFoundonce Redis removes expired fields.If Redis still behaves this way, the current doc and example would be misleading for Redis users. Please confirm current backend behavior and either:
- ensure all backends surface
ErrKeyExpiredfor TTL expiry, or- explicitly document that
ErrKeyExpiredis only produced by the in‑memory cache and that Redis maps expirations toErrKeyNotFoundso callers know how to interpret errors per backend.Based on learnings, this was a previously noted discrepancy; worth double‑checking it’s truly resolved.
internal/config/config.go (1)
125-128: Verify JWT Secret validation at startup.The default config initializes
JWT.TTLandJWT.Issuerbut leavesSecretempty. A past review flagged this as critical since an empty secret would make JWT tokens insecure.Verify that Secret validation or disabled-service fallback is implemented in the JWT module initialization:
#!/bin/bash # Description: Check if JWT module validates empty Secret or handles disabled state # Search for Secret validation in JWT module rg -nP --type=go -B2 -A5 'Secret.*==.*""' internal/sms-gateway/jwt/ # Check module initialization for Secret handling rg -nP --type=go -A10 'func.*Module\(\)' internal/sms-gateway/jwt/module.gogo.mod (1)
7-7: Pseudo-version dependency flagged in previous review.This concern has already been raised. The dependency should be updated to a stable release tag before merging.
internal/sms-gateway/users/cache.go (1)
51-81: Avoid using hashed credentials as cache keys inloginCache.
makeKeycurrently derives the cache key fromusernameandpasswordvia plain SHA‑256, andGet/Setuse this directly. This recreates several security issues previously raised:
- Deterministic hash enables correlation of identical credential pairs and offline guessing for common passwords.
- Cache effectively becomes an authentication oracle (cache hit implies valid creds).
- Password changes don’t naturally invalidate old entries keyed by the previous password.
- Keys derived directly from credentials are long‑lived sensitive artifacts (especially in Redis).
Prefer an opaque, non‑credential key, e.g.:
- Key by stable user identifier only (after successful authentication), or
- Derive a key using an HMAC with a server‑side secret and per‑user salt, without embedding the raw password.
Example direction (conceptual):
-func (c *loginCache) makeKey(username, password string) string { - hash := sha256.Sum256([]byte(username + "\x00" + password)) - return hex.EncodeToString(hash[:]) -} +func (c *loginCache) makeKey(userID string) string { + // e.g. prefix with namespace; you can also HMAC with a secret if needed + return "login:" + userID +} + -func (c *loginCache) Get(ctx context.Context, username, password string) (*User, error) { - user, err := c.storage.Get(ctx, c.makeKey(username, password), cache.AndSetTTL(loginCacheTTL)) +func (c *loginCache) Get(ctx context.Context, userID string) (*User, error) { + user, err := c.storage.Get(ctx, c.makeKey(userID), cache.AndSetTTL(loginCacheTTL))…with corresponding changes to
Set/Deleteand callers, and explicit invalidation on password change.api/requests.http (1)
200-218: Fix non-existentdevices:writescope in token request example.The scopes example for
POST /3rdparty/v1/auth/tokenstill includes"devices:write"(line 211), but earlier analysis showed only"devices:list"and"devices:delete"are defined in the devices permissions. This mismatch will produce tokens that can’t satisfy any authorization checks.Replace
"devices:write"with the correct device permission constant (most likely"devices:delete"), so all scopes in this example exactly match the defined permission strings.internal/sms-gateway/handlers/thirdparty/auth.go (2)
97-102: Validatejtiparameter is non-empty.The handler extracts
jtifrom path parameters but doesn't validate it before passing toRevokeToken. An emptyjtishould be rejected with a 400 Bad Request.Apply this diff:
func (h *AuthHandler) deleteToken(user users.User, c *fiber.Ctx) error { jti := c.Params("jti") + if jti == "" { + return fiber.NewError(fiber.StatusBadRequest, "jti parameter is required") + } if err := h.jwtSvc.RevokeToken(c.Context(), user.ID, jti); err != nil {
66-74: Add validation for TTL bounds before conversion.The
nolint:goseccomment claims TTL is "validated in the service," but the conversiontime.Duration(req.TTL)*time.Secondhappens in the handler. Ifreq.TTLis a largeuint64, the cast toint64(time.Duration) can overflow before the service ever sees it. Validatereq.TTLagainst a reasonable maximum (e.g., 31,536,000 seconds = 1 year) in the handler before conversion.+const maxTTLSeconds = 31536000 // 1 year + func (h *AuthHandler) postToken(user users.User, c *fiber.Ctx) error { req := new(smsgateway.TokenRequest) if err := h.BodyParserValidator(c, req); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } + if req.TTL > maxTTLSeconds { + return fiber.NewError(fiber.StatusBadRequest, "ttl exceeds maximum allowed value") + } + token, err := h.jwtSvc.GenerateToken( c.Context(), user.ID, req.Scopes, - time.Duration(req.TTL)*time.Second, //nolint:gosec // validated in the service + time.Duration(req.TTL)*time.Second, )internal/sms-gateway/handlers/devices/3rdparty.go (1)
94-97: Fix inconsistent route parameter syntax.Line 96 uses
:idwithout a leading slash, while the webhooks handler uses/:id. Per Fiber routing conventions, route parameters should include the leading slash for consistency.func (h *ThirdPartyController) Register(router fiber.Router) { router.Get("", permissions.RequireScope(ScopeList), userauth.WithUser(h.get)) - router.Delete(":id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove)) + router.Delete("/:id", permissions.RequireScope(ScopeDelete), userauth.WithUser(h.remove)) }internal/sms-gateway/users/service.go (2)
68-91: Security issue: Raw passwords still used in cache operations.Despite past review feedback, the
Loginmethod continues to pass plaintext passwords to the cache layer (lines 69, 86). Depending on the cache implementation (in-memory, Redis, metrics, logging), this risks propagating raw secrets into logs, metrics, or external systems.The recommended fix is to key the cache only on
username, retrieve the cached user, and then verify the password against the cachedPasswordHashusingcrypto.CompareBCryptHash:cachedUser, err := s.cache.Get(ctx, username) if err == nil { if crypto.CompareBCryptHash(cachedUser.PasswordHash, password) == nil { return cachedUser, nil } }And for setting:
s.cache.Set(ctx, username, *loggedInUser)
93-109: Critical issues in ChangePassword remain unaddressed.Two problems flagged in past reviews persist:
Raw password in cache (line 99): The
currentPasswordis still passed tocache.Delete, exposing plaintext credentials to the cache layer.Hard-fail on cache deletion (lines 99-101): The method returns immediately if cache deletion fails, potentially blocking password changes due to cache unavailability. Cache operations should be best-effort.
Apply these fixes:
func (s *Service) ChangePassword(ctx context.Context, username, currentPassword, newPassword string) error { _, err := s.Login(ctx, username, currentPassword) if err != nil { return err } - if delErr := s.cache.Delete(ctx, username, currentPassword); delErr != nil { - return delErr + // Best-effort cache invalidation - key only on username + if delErr := s.cache.Delete(ctx, username); delErr != nil { + s.logger.Warn("failed to delete login cache on password change", zap.String("username", username), zap.Error(delErr)) } passwordHash, err := crypto.MakeBCryptHash(newPassword) if err != nil { return fmt.Errorf("failed to hash password: %w", err) } return s.users.UpdatePassword(username, passwordHash) }internal/sms-gateway/handlers/messages/3rdparty.go (1)
327-331: Fix inconsistent route parameter syntax.Line 329 uses
:idwithout a leading slash, which is inconsistent with standard Fiber routing conventions and could cause routing issues.Apply this fix:
- router.Get(":id", permissions.RequireScope(ScopeRead), userauth.WithUser(h.get)).Name(route3rdPartyGetMessage) + router.Get("/:id", permissions.RequireScope(ScopeRead), userauth.WithUser(h.get)).Name(route3rdPartyGetMessage)internal/sms-gateway/users/repository.go (1)
54-62: Check RowsAffected to detect non-existent users.The
UpdatePasswordmethod does not check if any rows were updated. GORM'sUpdatedoes not return an error when zero rows are affected, so if the user ID doesn't exist, this method silently succeeds without updating anything. This is inconsistent withGetByIDreturningErrNotFoundfor non-existent users.Apply this fix:
func (r *repository) UpdatePassword(id string, passwordHash string) error { - if err := r.db.Model((*userModel)(nil)). + result := r.db.Model((*userModel)(nil)). Where("id = ?", id). - Update("password_hash", passwordHash).Error; err != nil { + Update("password_hash", passwordHash) + if err := result.Error; err != nil { return fmt.Errorf("can't update password: %w", err) } + if result.RowsAffected == 0 { + return ErrNotFound + } return nil }internal/sms-gateway/handlers/mobile.go (2)
43-75: Handlenanoid.Standarderror instead of discarding it.The constructor ignores the error from
nanoid.Standard(idLength). If the implementation ever changes or is misused,idGencould be nil and later calls toh.idGen()would panic. Better to handle this explicitly (e.g., by panicking with a clear message or returning an error from the constructor) rather than discarding the error.func newMobileHandler( @@ ) *mobileHandler { const idLength = 21 - idGen, _ := nanoid.Standard(idLength) + idGen, err := nanoid.Standard(idLength) + if err != nil { + // This should never happen with a constant, valid idLength, + // but fail fast rather than constructing a broken handler. + panic(fmt.Sprintf("failed to initialize ID generator: %v", err)) + } @@ - idGen: idGen, + idGen: idGen, } }
235-238: Distinguish auth failures from internal errors inchangePassword.
usersSvc.ChangePasswordcan plausibly fail for both auth reasons (wrong current password, unknown user) and internal ones (cache/DB errors, hashing issues). Mapping all failures to 401 both misleads clients and hides server problems; the swagger above also documents a 500.Consider something along these lines (using your actual sentinel error(s)):
- if err := h.usersSvc.ChangePassword(c.Context(), device.UserID, req.CurrentPassword, req.NewPassword); err != nil { - h.Logger.Error("failed to change password", zap.Error(err)) - return fiber.NewError(fiber.StatusUnauthorized, "failed to change password") - } + if err := h.usersSvc.ChangePassword(c.Context(), device.UserID, req.CurrentPassword, req.NewPassword); err != nil { + h.Logger.Error("failed to change password", zap.Error(err)) + + // Replace `users.ErrInvalidPassword` (and any others) with the real auth error(s) + if errors.Is(err, users.ErrInvalidPassword) { + return fiber.NewError(fiber.StatusUnauthorized, "invalid current password") + } + + return fiber.NewError(fiber.StatusInternalServerError, "failed to change password") + }This keeps client-visible behavior clear while still logging the full underlying error.
pkg/cache/redis.go (2)
25-52: Fix HTTL result handling in Lua script (it returns an array).
HTTLreturns an array of integers (one per field), not a single integer scalar. The current script treats the result as a number (if ttl < 0,ttl + ttlDelta), which will raise a Lua runtime error when this branch executes. (redis.io)Because this script backs
GetwithAndUpdateTTL/AndDefaultTTLbehavior, the bug can break cache operations that rely on TTL extension.Suggested fix:
elseif ttlDelta > 0 then - local ttl = redis.call('HTTL', KEYS[1], 'FIELDS', '1', field) - if ttl < 0 then - ttl = 0 - end - local newTtl = ttl + ttlDelta - redis.call('HExpire', KEYS[1], newTtl, 'FIELDS', '1', field) + local ttlArr = redis.call('HTTL', KEYS[1], 'FIELDS', '1', field) + local ttl = ttlArr[1] + if ttl < 0 then + ttl = 0 + end + local newTtl = ttl + ttlDelta + redis.call('HExpire', KEYS[1], newTtl, 'FIELDS', '1', field) endThis matches the documented HTTL response shape and preserves the intended “extend TTL by delta, treating missing/no‑TTL as zero” behavior. (upstash.com)
55-99: Clarify Redis version requirement inRedisCachedocs.The implementation relies on Redis 7.4.0+ hash field TTL commands (
HEXPIREAT,HTTL,HEXPIREwithFIELDS). Older Redis versions will fail at runtime on these commands. (redis.io)Consider adding an explicit note to the
RedisCachetype comment:-// RedisCache implements the Cache interface using Redis as the backend. -// -// This implementation stores all data in a Redis hash, with each cache item +// RedisCache implements the Cache interface using Redis as the backend. +// +// Requires Redis 7.4.0 or later for per-field hash TTL support (HEXPIREAT, HTTL, HEXPIRE). +// +// This implementation stores all data in a Redis hash, with each cache item // being a field in the hash. It uses Redis's built-in TTL functionality for // expiration and Lua scripts for atomic operations.Based on learnings, this will make the minimum Redis version expectation explicit for operators.
🧹 Nitpick comments (8)
pkg/cache/errors.go (1)
53-53: Consider documenting ErrFailedToCreateZeroValue and aligning its message
ErrFailedToCreateZeroValueis exported but currently undocumented, and its message says “zero item” while the name says “zero value”. Suggest adding a brief doc comment and tightening the message for consistency, e.g.:- ErrFailedToCreateZeroValue = errors.New("failed to create zero item") + // ErrFailedToCreateZeroValue indicates a failure to construct the zero value for an item. + ErrFailedToCreateZeroValue = errors.New("failed to create zero value")This keeps the public API self‑documenting and avoids naming/message drift.
api/mobile.http (1)
71-72: Test payload sets identical current and new passwords.Both
currentPasswordandnewPasswordare set to the same value. This won't effectively test the password change functionality. Consider using different values to validate the endpoint behavior.Note: The Gitleaks warnings about "Generic API Key" are false positives—these are test credentials in a development
.httpfile.{ - "currentPassword": "8f8ijpnuvemq7y", - "newPassword": "8f8ijpnuvemq7y" + "currentPassword": "oldPassword123", + "newPassword": "newPassword456" }internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
14-41: Clarify claim semantics vs. user lookup and consider Fiber context choice.Two small points to tighten here:
- The claim field is named
UserIDbut you callusersSvc.GetByUsername(claims.UserID). If the claim really stores a username, consider renaming the field (and the jwt.Service parameter) to avoid confusion; if it stores the user’s ID, this middleware should likely call aGetByIDvariant instead. The current naming/usage mismatch is easy to misread later.- You're passing
c.Context()intojwtSvc.ParseToken. In many Fiber versions the requestcontext.Contextis exposed viac.UserContext(), whilec.Context()refers to the underlying fasthttp context. Please double‑check this against the Fiber version in use and switch to the appropriate method if needed.pkg/cache/cache.go (1)
1-283: Double-check option helper names in Cache examples to match actual API.The
Cacheinterface and overall semantics look good and align with the memory/Redis behaviors (only non-expired items inDrain,Cleanupas explicit expiry sweep), but a couple of examples may be out of sync with the actual helpers:
Getexample usescache.AndUpdateTTL(30*time.Minute), while the provided snippets showAndSetTTLas the TTL-relatedGetOption.Setexamples referenceWithValidUntilin comments, but onlyWithTTLis shown inoptions.go.Please confirm the real helper names in
options.goand update the examples if necessary so that the docs compile mentally for readers and match the concrete API.internal/sms-gateway/jwt/repository.go (1)
38-47: Consider optimizing the existence check.The current implementation uses
Countto check if a token is revoked. For existence checks, aLIMIT 1query is typically more efficient:var exists bool err := r.db.WithContext(ctx). Model((*tokenModel)(nil)). Select("1"). Where("id = ? and revoked_at is not null", jti). Limit(1). Find(&exists).ErrorHowever, the current implementation is correct and readable, so this optimization is optional.
pkg/cache/redis.go (2)
101-113: Fix return type name inNewRedisdocumentation.The doc comment says the function returns
*redisCache, but the actual exported type is*RedisCache. Small but worth correcting for clarity and tooling.-// Returns: -// -// - *redisCache: A new Redis cache instance +// Returns: +// +// - *RedisCache: A new Redis cache instance
468-469: Align compile-time check comment with exported type name.The comment still refers to
redisCachewhile the type isRedisCache. Not a functional issue, but aligning the name avoids confusion.-// Compile-time check to ensure redisCache implements the Cache interface. +// Compile-time check to ensure RedisCache implements the Cache interface. var _ Cache = (*RedisCache)(nil)pkg/cache/memory.go (1)
436-453: Minor doc nit: type name in compile-time check comment.Tiny documentation mismatch: the comment says
memoryCachebut the actual type isMemoryCache. Not harmful, but easy to tidy up.-// Compile-time check to ensure memoryCache implements the Cache interface. +// Compile-time check to ensure MemoryCache implements the Cache interface. var _ Cache = (*MemoryCache)(nil)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (66)
.golangci.yml(2 hunks)api/mobile.http(1 hunks)api/requests.http(5 hunks)cmd/sms-gateway/main.go(1 hunks)configs/config.example.yml(1 hunks)deployments/grafana/dashboards/jwt.json(1 hunks)deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)go.mod(2 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/base/handler.go(2 hunks)internal/sms-gateway/handlers/base/handler_test.go(9 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(4 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(5 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(5 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/jwt/config.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/metrics.go(1 hunks)internal/sms-gateway/jwt/models.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/repository.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/cache.go(0 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(2 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/openapi/docs.go(31 hunks)internal/sms-gateway/users/cache.go(1 hunks)internal/sms-gateway/users/domain.go(1 hunks)internal/sms-gateway/users/errors.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)internal/sms-gateway/users/service.go(1 hunks)pkg/cache/cache.go(1 hunks)pkg/cache/errors.go(1 hunks)pkg/cache/memory.go(13 hunks)pkg/cache/options.go(4 hunks)pkg/cache/redis.go(10 hunks)pkg/cache/typed.go(1 hunks)
💤 Files with no reviewable changes (4)
- internal/sms-gateway/models/models.go
- internal/sms-gateway/modules/auth/repository.go
- internal/sms-gateway/modules/auth/module.go
- internal/sms-gateway/modules/auth/cache.go
✅ Files skipped from review due to trivial changes (2)
- internal/sms-gateway/handlers/settings/permissions.go
- deployments/grafana/dashboards/jwt.json
🚧 Files skipped from review as they are similar to previous changes (18)
- internal/sms-gateway/handlers/devices/permissions.go
- internal/sms-gateway/users/models.go
- internal/sms-gateway/app.go
- internal/sms-gateway/modules/webhooks/models.go
- internal/sms-gateway/handlers/thirdparty/module.go
- internal/sms-gateway/handlers/webhooks/permissions.go
- internal/sms-gateway/jwt/config.go
- internal/config/types.go
- internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql
- internal/sms-gateway/jwt/errors.go
- internal/sms-gateway/handlers/base/handler.go
- internal/sms-gateway/handlers/logs/permissions.go
- deployments/prometheus/alerts/jwt-alerts.yml
- internal/sms-gateway/modules/messages/service.go
- internal/sms-gateway/users/module.go
- internal/sms-gateway/handlers/thirdparty/permissions.go
- cmd/sms-gateway/main.go
- internal/config/module.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-10T23:53:42.006Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:63-75
Timestamp: 2025-09-10T23:53:42.006Z
Learning: In pkg/cache/redis.go, the Redis cache implementation uses Redis's native field-level TTL via HExpireAt, meaning expired fields are automatically removed by Redis itself. This differs from the memory implementation where the application tracks expiration times. As a result, operations like Drain that call HGetAll will only return non-expired items since expired fields are already removed by Redis.
Applied to files:
pkg/cache/errors.gopkg/cache/redis.gopkg/cache/options.gopkg/cache/memory.gopkg/cache/cache.go
📚 Learning: 2025-09-10T23:54:16.828Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/redis.go:118-121
Timestamp: 2025-09-10T23:54:16.828Z
Learning: Redis 7.4.0 introduced HEXPIRE, HEXPIREAT, and related commands that support per-field TTL on hash data structures. go-redis v9.6.0+ includes support for these commands with methods like HExpireAt.
Applied to files:
pkg/cache/errors.gopkg/cache/redis.go
📚 Learning: 2025-09-10T11:11:43.462Z
Learnt from: capcom6
Repo: android-sms-gateway/server PR: 178
File: pkg/cache/memory.go:59-74
Timestamp: 2025-09-10T11:11:43.462Z
Learning: In pkg/cache/memory.go, the cleanup method removes expired items first, then executes the provided callback function. This means methods like Drain that use cleanup don't need additional expiration filtering since expired items are already removed before the callback runs.
Applied to files:
pkg/cache/redis.gopkg/cache/memory.gopkg/cache/cache.go
🧬 Code graph analysis (25)
internal/sms-gateway/handlers/module.go (1)
internal/sms-gateway/handlers/thirdparty/module.go (1)
Module(8-16)
internal/sms-gateway/handlers/logs/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/logs/permissions.go (1)
ScopeRead(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
pkg/cache/typed.go (3)
pkg/cache/cache.go (1)
Cache(112-283)pkg/cache/options.go (3)
Option(9-9)GetOption(90-90)AndDelete(205-209)pkg/cache/errors.go (1)
ErrFailedToCreateZeroValue(53-53)
internal/sms-gateway/handlers/webhooks/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/webhooks/permissions.go (1)
ScopeList(4-4)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/jwt/module.go (4)
internal/sms-gateway/jwt/repository.go (2)
NewRepository(14-18)Repository(10-12)internal/sms-gateway/jwt/jwt.go (1)
Service(10-14)internal/sms-gateway/jwt/service.go (1)
New(24-51)internal/sms-gateway/jwt/models.go (1)
Migrate(33-38)
internal/sms-gateway/users/cache.go (5)
pkg/cache/typed.go (2)
Typed(89-91)NewTyped(118-122)internal/config/config.go (1)
Cache(78-80)internal/sms-gateway/users/domain.go (1)
User(5-10)pkg/cache/options.go (2)
AndSetTTL(123-127)WithTTL(39-50)pkg/cache/errors.go (2)
ErrKeyNotFound(19-19)ErrKeyExpired(36-36)
internal/sms-gateway/jwt/disabled.go (2)
internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (1)
ErrDisabled(6-6)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (2)
SetScopes(15-17)ScopeAll(10-10)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/users/service.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/users/errors.go (1)
ErrExists(7-7)pkg/crypto/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)pkg/cache/errors.go (1)
ErrKeyNotFound(19-19)
internal/sms-gateway/users/repository.go (1)
internal/sms-gateway/users/errors.go (1)
ErrNotFound(6-6)
internal/sms-gateway/jwt/service.go (5)
internal/sms-gateway/jwt/config.go (1)
Config(12-16)internal/sms-gateway/jwt/repository.go (1)
Repository(10-12)internal/sms-gateway/jwt/metrics.go (3)
Metrics(24-31)StatusError(20-20)StatusSuccess(19-19)internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (4)
ErrInitFailed(7-7)ErrInvalidParams(9-9)ErrInvalidToken(10-10)ErrTokenRevoked(11-11)
pkg/cache/redis.go (1)
pkg/cache/options.go (1)
AndDelete(205-209)
internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (3)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
SetUser(88-90)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
SetScopes(15-17)
internal/sms-gateway/handlers/devices/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/devices/permissions.go (2)
ScopeList(4-4)ScopeDelete(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/handlers/3rdparty.go (6)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/handlers/logs/3rdparty.go (1)
ThirdPartyController(21-23)internal/sms-gateway/handlers/thirdparty/auth.go (1)
AuthHandler(19-23)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
NewBasic(21-59)UserRequired(116-124)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go (1)
NewJWT(14-42)
internal/sms-gateway/handlers/messages/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/messages/permissions.go (4)
ScopeList(10-10)ScopeSend(6-6)ScopeRead(8-8)ScopeExport(12-12)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/handlers/settings/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/settings/permissions.go (2)
ScopeRead(4-4)ScopeWrite(5-5)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/config/config.go (2)
internal/config/types.go (1)
Duration(11-11)internal/worker/config/types.go (1)
Duration(11-11)
pkg/cache/memory.go (1)
pkg/cache/options.go (1)
AndDelete(205-209)
internal/sms-gateway/handlers/mobile.go (4)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
GetUser(103-110)NewBasic(21-59)
internal/sms-gateway/modules/auth/service.go (3)
internal/sms-gateway/users/service.go (1)
Service(13-19)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/models/models.go (2)
Device(18-29)NewDevice(31-37)
internal/sms-gateway/jwt/models.go (5)
internal/sms-gateway/models/models.go (1)
TimedModel(7-10)internal/sms-gateway/models/migration.go (1)
Migrate(13-18)internal/sms-gateway/modules/settings/models.go (1)
Migrate(28-33)internal/sms-gateway/modules/webhooks/models.go (1)
Migrate(39-44)internal/sms-gateway/users/models.go (1)
Migrate(29-34)
internal/sms-gateway/modules/settings/models.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/models/migration.go (1)
internal/sms-gateway/models/models.go (1)
Device(18-29)
pkg/cache/cache.go (1)
pkg/cache/options.go (2)
Option(9-9)GetOption(90-90)
🪛 Gitleaks (8.30.0)
api/mobile.http
[high] 71-71: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 72-72: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Lint
- GitHub Check: Benchmark
- GitHub Check: Test
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (37)
.golangci.yml (1)
255-260: Verify that linter exclusion patterns align with actual project dependencies.The changes add exclusion patterns for third-party types from the
exhaustructlinter check:
- Firebase Messaging (AndroidConfig, Message)
- GoFiber Config (using regex
.+Config)- JWT Claims from golang-jwt/jwt/v5 (using regex
.+Claims)- Telebot types (LongPoller, ReplyMarkup, Settings)
The patterns appear syntactically correct and are appropriate for external library types that commonly have optional fields. However, the regex patterns (
.+Config,.+Claims) are relatively broad and will match any type name matching those suffixes/patterns.These changes align well with the PR's objective to add JWT authentication and integrate GoFiber. The exclusions are reasonable for configuration and claims structures where exhaustive initialization is impractical.
Please confirm that:
- The regex patterns (e.g.,
github.com/gofiber/.+Config,github.com/golang-jwt/jwt/v5.+Claims) match the intended types without unintended side effects- All listed dependencies are actually used in the codebase (verify against go.mod)
You can verify this by checking if the linter runs cleanly and that no unintended exclusions are occurring.
Also applies to: 276-278
pkg/cache/errors.go (2)
6-19: ErrKeyNotFound documentation is clear and consistentThe expanded comment and example around
ErrKeyNotFoundread well and accurately describe missing-key semantics forGet/GetAndDelete. No issues from my side here.
38-51: ErrKeyExists docs and example look goodThe description of
ErrKeyExistsin terms ofSetOrFailand create‑if‑absent semantics is precise and the example is straightforward. Nothing to change here.internal/sms-gateway/handlers/module.go (1)
9-9: LGTM!The thirdparty module import and wiring follow the existing fx module composition pattern. The integration correctly includes
thirdparty.Module()as an option within the handlers module.Also applies to: 41-41
internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
1-36: LGTM! Clean permissions middleware implementation.The implementation follows good practices:
- Type-safe scope storage with proper type assertion handling
ScopeAllwildcard support for admin-level access- Clear error response for missing scopes
Minor consideration: the 403 response includes the required scope name (
"scope required: "+scope), which is helpful for debugging but could be considered information disclosure. This is typically acceptable for internal APIs.internal/sms-gateway/handlers/messages/permissions.go (1)
1-13: LGTM! Well-documented permission scope constants.Clear naming convention (
messages:<action>) and appropriate documentation for each scope. The separation ofScopeListfromScopeReadallows fine-grained access control.internal/sms-gateway/modules/auth/service.go (2)
85-96: LGTM! Clean device registration with user service integration.The
RegisterDevicemethod properly uses the newusers.Usertype and delegates to the devices service. The by-value parameter forusers.Useris acceptable given the struct contains only simple fields.
35-53: LGTM! Explicit dependency injection improves clarity.The constructor now accepts explicit dependencies rather than an fx.Params bundle, making the service's requirements immediately visible and easier to test.
internal/sms-gateway/jwt/disabled.go (1)
1-28: LGTM! Clean null-object pattern for disabled JWT service.This implementation provides a graceful fallback when JWT is not configured:
- Consistently returns
ErrDisabledfor all operations- Unexported type with exported interface is idiomatic
- Enables module wiring to provide a safe default without nil checks
This complements the config's empty Secret handling by providing a safe disabled state rather than failing at startup.
go.mod (1)
18-18: LGTM on jwt/v5 v5.3.0.This version is not affected by the known vulnerabilities (CVE-2025-30204 was fixed in v5.2.2, and CVE-2024-51744 affected only v4.x).
internal/sms-gateway/modules/settings/models.go (1)
7-7: LGTM on User type migration.The change from
models.Usertousers.Useraligns with the new users domain package. The foreign key constraint is preserved correctly.Also applies to: 17-17
internal/sms-gateway/jwt/module.go (2)
14-20: LGTM on conditional service initialization.The pattern of returning a disabled service when
config.Secretis empty provides a clean opt-out mechanism for JWT authentication.
24-27: LGTM on migration registration.The
init()pattern for migration registration is consistent with the framework's conventions, and thenolintdirective appropriately documents the intentional use.internal/sms-gateway/handlers/logs/3rdparty.go (2)
37-37: LGTM on OpenAPI documentation updates.The addition of
JWTAuthsecurity and403 Forbiddenresponse correctly documents the new authentication and authorization behavior.Also applies to: 44-44
50-50: LGTM on permission-gated handler.The middleware chain correctly applies scope checking before user extraction, and the handler signature aligns with the
userauth.WithUserwrapper pattern.Also applies to: 58-58
internal/sms-gateway/users/errors.go (1)
5-8: LGTM on sentinel error definitions.Clean, idiomatic Go error values for user repository operations.
configs/config.example.yml (1)
41-44: JWT config block looks consistent with the new module.
jwt.secret,jwt.ttl(24h), andjwt.issuermatch the new JWT config surface and follow the same duration style as other intervals. No issues from the sample config side.internal/sms-gateway/jwt/models.go (1)
11-38: Token persistence model & migration look sound.
tokenModelshape, table name, indexes, and theMigratehelper are consistent with existing model patterns and should work well for token lookup and cleanup.internal/sms-gateway/users/domain.go (1)
5-19: DomainUsertype and mapper are straightforward and correct.The exported
UserandnewUsermapper cleanly separate domain from persistence without extra coupling. Looks good.internal/sms-gateway/jwt/jwt.go (1)
10-27: JWT service interface and claim types look well-structured.The
Serviceinterface andClaims/TokenInfotypes provide a clear, minimal surface for token generation, parsing, and revocation, and should be easy to integrate across handlers and middleware.internal/sms-gateway/handlers/settings/3rdparty.go (1)
140-144: LGTM! Consistent permission enforcement across endpoints.The route registrations correctly apply scope-based permissions with
ScopeReadfor GET andScopeWritefor PUT/PATCH, matching the OpenAPI annotations and the permission middleware pattern.internal/sms-gateway/handlers/thirdparty/auth.go (1)
107-130: Error handler provides clear error mapping.The error handler correctly translates domain-specific JWT errors to appropriate HTTP status codes with user-friendly messages. The fallthrough for
ErrInitFailedandErrInvalidConfigis a reasonable choice for similar server-side issues.internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
123-127: LGTM! Well-structured permission enforcement.Route registrations correctly apply granular scopes (
ScopeList,ScopeWrite,ScopeDelete) and the delete route uses proper Fiber syntax with/:id.internal/sms-gateway/openapi/docs.go (1)
1-6: Generated OpenAPI spec correctly reflects handler annotations.The generated documentation properly includes:
- New token endpoints with both
ApiAuthandJWTAuthsecurity- 403 Forbidden responses across all protected endpoints
jtipath parameter for token revocationTokenRequestandTokenResponseschema definitionsSince this is auto-generated, any remaining issues should be addressed in the source handler files.
pkg/cache/options.go (1)
76-209: LGTM!The new GetOption API is well-designed and thoroughly documented. The option pattern is implemented correctly, and the examples clearly demonstrate the intended usage. The ability to perform atomic operations like get-and-delete or update TTL during retrieval provides useful flexibility.
internal/sms-gateway/jwt/repository.go (1)
28-36: Verify idempotent revocation behavior.The
Revokemethod does not checkRowsAffected, so it silently succeeds when revoking a non-existent or already-revoked token. If idempotent revocation is the intended behavior, this is fine. However, if callers need to know whether a token was actually revoked, consider checkingRowsAffectedand returning an error or specific sentinel value when zero rows are affected.internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
21-141: LGTM!The authentication middleware has been correctly updated to use the new
users.Usertype and integrate with the permissions system. The user storage and retrieval logic is now consistent across all authentication paths (NewBasic,NewCode), and the nil checks inWithUserprevent potential panics.pkg/cache/typed.go (1)
1-407: LGTM!The typed cache implementation is well-designed and correctly addresses the pointer instantiation issue flagged in previous reviews. The
newItem[T]()helper ensures that pointer types are properly instantiated before unmarshaling, preventing nil pointer dereferences. The API is clean, well-documented, and provides a type-safe wrapper around the cache interface.internal/sms-gateway/jwt/service.go (2)
53-117: LGTM!The
GenerateTokenmethod correctly validates all input parameters, including the userID validation that addresses past review feedback. The TTL capping at line 89 (min(ttl, s.config.TTL)) enforces a maximum TTL from configuration, which is a reasonable security policy even if it may reduce a caller's requested TTL.
119-182: LGTM!The
ParseTokenandRevokeTokenmethods implement proper token validation and revocation flows with appropriate error handling and metrics tracking.internal/sms-gateway/handlers/messages/3rdparty.go (1)
56-282: LGTM!The handlers have been correctly updated to use the
users.Usertype and integrate with the permissions system. The scope-based authorization is properly applied to each endpoint, and the documentation reflects the JWT authentication support.internal/sms-gateway/users/repository.go (1)
21-44: LGTM!The
ExistsandGetByIDmethods are correctly implemented. The past review issue regardingGetByIDpassing the wrong pointer type has been properly addressed.internal/sms-gateway/handlers/mobile.go (2)
201-212:getUserCodesignature and usage look consistent.Switching the parameter to
users.Userand usinguser.IDwithGenerateUserCodematches the new users domain model and the expecteduserauth.WithUsercallback shape. No issues here.
243-272: Auth wiring for mobile routes is coherent with the new users service.Injecting
usersSvcinto the handler and reusinguserauth.NewBasic(h.usersSvc)for/mobile/v1/deviceand/mobile/v1/user/code, plususerauth.WithUser(h.getUserCode), lines up with the new user-based auth model and the userauth/jwtauth middlewares. No functional issues spotted in this wiring.internal/sms-gateway/jwt/metrics.go (1)
8-117: JWT metrics implementation looks solid and idiomatic.Metric names, labels, and the
MetricsAPI (increment + observe wrappers) are clean, thread‑safe, and consistent with Prometheus best practices. The shared bucket configuration across issuance/validation/revocation also makes sense for latency SLOs.internal/sms-gateway/handlers/3rdparty.go (1)
20-80: JWT and user service wiring for 3rd‑party handler looks correct.Injecting
usersSvcandjwtSvcintothirdPartyHandler, then composinguserauth.NewBasic(h.usersSvc),jwtauth.NewJWT(h.jwtSvc, h.usersSvc), anduserauth.UserRequired()inRegistermatches the new auth model and the middleware contracts fromjwtauthanduserauth. The explicit constructor also makes DI clearer than the old fx params.pkg/cache/memory.go (1)
30-365: In-memory cache TTL and concurrency behavior look correct.The exported
MemoryCachenow has a clear API, and the internal mechanics line up with the documented semantics:
ttl == 0truly means “no default expiration”, including in thedefaultTTLpath inGet.Getwith options performs delete/TTL updates under a write lock and then funnels throughgetItem/getValue, preserving theErrKeyNotFoundvsErrKeyExpireddistinction.Drainusescleanupto strip expired items before snapshotting and clearing the map, so callers only see non‑expired entries (matching the prior design).No functional issues spotted here.
7a69564 to
e563572
Compare
f96266f to
f5a90ef
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (11)
api/requests.http (1)
207-217: Scopedevices:writedoes not exist - usedevices:deleteinstead.Per previous review findings,
devices/permissions.goonly definesScopeList = "devices:list"andScopeDelete = "devices:delete". The scope"devices:write"on line 211 is invalid and should be replaced with"devices:delete"to match the defined permission constants.internal/config/config.go (1)
125-128: Reminder: Validate JWT Secret at startup.This is a duplicate of a previously flagged issue. The default configuration leaves Secret empty, which must be addressed through startup validation as discussed in the earlier review.
internal/sms-gateway/users/service.go (2)
67-90: Security concern already flagged: Raw passwords in cache operations.This is a duplicate of previously flagged issues. The Login method passes plaintext passwords to cache operations (lines 68, 85), which poses a security risk as discussed in earlier reviews.
92-108: Hard-fail on cache deletion already flagged.This is a duplicate of a previously flagged issue. The ChangePassword method returns immediately on cache.Delete error (lines 98-100), potentially blocking password changes due to cache issues, as discussed in earlier reviews.
internal/sms-gateway/handlers/mobile.go (3)
57-57: Error handling issue already flagged.This is a duplicate of a previously flagged critical issue. The error from
nanoid.Standard(21)is ignored, which could lead to a nil pointer panic whenh.idGen()is called.
138-147: Error handling inconsistency already flagged.This is a duplicate of a previously flagged issue. The use of
fmt.Errorfinstead offiber.NewErrorfor operational failures is inconsistent with the pattern used elsewhere in this file, as discussed in earlier reviews.
235-237: Error type distinction issue already flagged.This is a duplicate of a previously flagged issue. All ChangePassword errors return 401, but internal errors should return 500 as discussed in earlier reviews.
internal/sms-gateway/handlers/devices/3rdparty.go (1)
95-96: Route parameter syntax issue already flagged.This is a duplicate of a previously flagged issue. Line 96 uses
:idwithout a leading slash, which is inconsistent with Fiber conventions as discussed in earlier reviews.internal/sms-gateway/handlers/thirdparty/auth.go (2)
66-74: TTL validation still missing - potential integer overflow.The conversion
time.Duration(req.TTL)*time.Secondon line 70 can overflow for largereq.TTLvalues sincetime.Durationisint64and the request field may accept very large values. ValidateTTLbounds before conversion to prevent unexpected token lifetimes.func (h *AuthHandler) postToken(user users.User, c *fiber.Ctx) error { req := new(smsgateway.TokenRequest) if err := h.BodyParserValidator(c, req); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } + const maxTTLSeconds = 365 * 24 * 60 * 60 // 1 year + if req.TTL > maxTTLSeconds { + return fiber.NewError(fiber.StatusBadRequest, "TTL exceeds maximum allowed value") + } + token, err := h.jwtSvc.GenerateToken( c.Context(), user.ID, req.Scopes, time.Duration(req.TTL)*time.Second, )
97-105: Validatejtiparameter is non-empty.The handler extracts
jtifrom path parameters but doesn't validate it before callingRevokeToken. An emptyjtishould be rejected at the handler level with a 400 Bad Request to fail fast with a clear error.func (h *AuthHandler) deleteToken(user users.User, c *fiber.Ctx) error { jti := c.Params("jti") + if jti == "" { + return fiber.NewError(fiber.StatusBadRequest, "jti parameter is required") + } if err := h.jwtSvc.RevokeToken(c.Context(), user.ID, jti); err != nil { return fmt.Errorf("failed to revoke token: %w", err) } return c.SendStatus(fiber.StatusNoContent) }internal/sms-gateway/handlers/messages/3rdparty.go (1)
327-331: Route parameter syntax is inconsistent with other handlers.Line 329 uses
:idwithout a leading slash, while the webhooks handler (line 126 in webhooks/3rdparty.go) uses/:id. This inconsistency was flagged in a previous review but hasn't been addressed.- router.Get(":id", permissions.RequireScope(ScopeRead), userauth.WithUser(h.get)).Name(route3rdPartyGetMessage) + router.Get("/:id", permissions.RequireScope(ScopeRead), userauth.WithUser(h.get)).Name(route3rdPartyGetMessage)
🧹 Nitpick comments (6)
internal/sms-gateway/users/models.go (1)
10-15: userModel layout looks good; consider future‑proofing password hash lengthStruct setup with an unexported GORM model and embedded
SoftDeletableModelis clean and keeps persistence details internal. Thevarchar(72)forPasswordHashis fine for bcrypt‑style hashes but could be tight if you ever switch to longer encodings (e.g., some Argon2 variants). Consider bumping this to something more generous (e.g., 128–255) to avoid a schema change later.- PasswordHash string `gorm:"not null;type:varchar(72)"` + PasswordHash string `gorm:"not null;type:varchar(255)"`deployments/grafana/dashboards/jwt.json (1)
36-39: Hardcoded Prometheus datasource UID reduces portability.The datasource UID
"edqp0a73uh2bka"is hardcoded throughout the dashboard. This will break when imported into a different Grafana instance. Consider using a variable like"${DS_PROMETHEUS}"or the default"Prometheus"name reference for better portability across environments.internal/sms-gateway/jwt/jwt.go (1)
23-27: Consider adding JSON tags to TokenInfo if serialized in API responses.If
TokenInfois returned directly in HTTP responses, the field names will use Go's default capitalization (e.g.,ID,AccessToken). Adding JSON tags would ensure consistent API naming conventions (e.g.,"id","access_token").#!/bin/bash # Check if TokenInfo is used in API response serialization echo "=== TokenInfo usage in handlers ===" rg -n 'TokenInfo' internal/sms-gateway/handlers/internal/sms-gateway/handlers/devices/permissions.go (1)
3-6: Consider adding documentation comments for consistency.The
messages/permissions.gofile includes documentation comments for each scope constant, but this file does not. For consistency across the codebase, consider adding brief doc comments.const ( + // ScopeList is the permission scope required for listing devices. ScopeList = "devices:list" + // ScopeDelete is the permission scope required for deleting devices. ScopeDelete = "devices:delete" )internal/sms-gateway/jwt/repository.go (1)
28-36: Consider returning whether token was actually revoked.The
Revokemethod succeeds silently if the token doesn't exist or is already revoked. While this may be intentional for idempotency, consider checkingRowsAffectedif callers need to know whether revocation actually occurred.func (r *Repository) Revoke(ctx context.Context, jti, userID string) error { - if err := r.db.WithContext(ctx).Model((*tokenModel)(nil)). + result := r.db.WithContext(ctx).Model((*tokenModel)(nil)). Where("id = ? and user_id = ? and revoked_at is null", jti, userID). - Update("revoked_at", gorm.Expr("NOW()")).Error; err != nil { + Update("revoked_at", gorm.Expr("NOW()")) + if err := result.Error; err != nil { return fmt.Errorf("can't revoke token: %w", err) } + // Optionally: if result.RowsAffected == 0 { return ErrTokenNotFound } return nil }internal/sms-gateway/handlers/3rdparty.go (1)
35-67: Consider using an options struct for the growing parameter list.The constructor now accepts 10+ parameters. While functional, this can become unwieldy and order-dependent. An options/params struct would improve readability and make future additions easier.
+type ThirdPartyHandlerDeps struct { + UsersSvc *users.Service + JWTService jwt.Service + HealthHandler *HealthHandler + MessagesHandler *messages.ThirdPartyController + WebhooksHandler *webhooks.ThirdPartyController + DevicesHandler *devices.ThirdPartyController + SettingsHandler *settings.ThirdPartyController + LogsHandler *logs.ThirdPartyController + AuthHandler *thirdparty.AuthHandler + Logger *zap.Logger + Validator *validator.Validate +} + +func newThirdPartyHandler(deps ThirdPartyHandlerDeps) *thirdPartyHandler { // use deps.UsersSvc, deps.JWTService, etc. +}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (62)
.golangci.yml(2 hunks)api/mobile.http(1 hunks)api/requests.http(5 hunks)cmd/sms-gateway/main.go(1 hunks)configs/config.example.yml(1 hunks)deployments/grafana/dashboards/jwt.json(1 hunks)deployments/prometheus/alerts/jwt-alerts.yml(1 hunks)go.mod(2 hunks)internal/config/config.go(4 hunks)internal/config/module.go(2 hunks)internal/config/types.go(1 hunks)internal/sms-gateway/app.go(4 hunks)internal/sms-gateway/handlers/3rdparty.go(2 hunks)internal/sms-gateway/handlers/base/handler.go(2 hunks)internal/sms-gateway/handlers/base/handler_test.go(9 hunks)internal/sms-gateway/handlers/devices/3rdparty.go(4 hunks)internal/sms-gateway/handlers/devices/permissions.go(1 hunks)internal/sms-gateway/handlers/logs/3rdparty.go(2 hunks)internal/sms-gateway/handlers/logs/permissions.go(1 hunks)internal/sms-gateway/handlers/messages/3rdparty.go(8 hunks)internal/sms-gateway/handlers/messages/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go(1 hunks)internal/sms-gateway/handlers/middlewares/permissions/permissions.go(1 hunks)internal/sms-gateway/handlers/middlewares/userauth/userauth.go(4 hunks)internal/sms-gateway/handlers/mobile.go(8 hunks)internal/sms-gateway/handlers/module.go(2 hunks)internal/sms-gateway/handlers/settings/3rdparty.go(5 hunks)internal/sms-gateway/handlers/settings/permissions.go(1 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/handlers/thirdparty/module.go(1 hunks)internal/sms-gateway/handlers/thirdparty/permissions.go(1 hunks)internal/sms-gateway/handlers/webhooks/3rdparty.go(5 hunks)internal/sms-gateway/handlers/webhooks/permissions.go(1 hunks)internal/sms-gateway/jwt/config.go(1 hunks)internal/sms-gateway/jwt/disabled.go(1 hunks)internal/sms-gateway/jwt/errors.go(1 hunks)internal/sms-gateway/jwt/jwt.go(1 hunks)internal/sms-gateway/jwt/metrics.go(1 hunks)internal/sms-gateway/jwt/models.go(1 hunks)internal/sms-gateway/jwt/module.go(1 hunks)internal/sms-gateway/jwt/repository.go(1 hunks)internal/sms-gateway/jwt/service.go(1 hunks)internal/sms-gateway/models/migration.go(1 hunks)internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql(1 hunks)internal/sms-gateway/models/models.go(0 hunks)internal/sms-gateway/modules/auth/cache.go(0 hunks)internal/sms-gateway/modules/auth/module.go(0 hunks)internal/sms-gateway/modules/auth/repository.go(0 hunks)internal/sms-gateway/modules/auth/service.go(4 hunks)internal/sms-gateway/modules/messages/service.go(3 hunks)internal/sms-gateway/modules/settings/models.go(2 hunks)internal/sms-gateway/modules/webhooks/models.go(2 hunks)internal/sms-gateway/openapi/docs.go(31 hunks)internal/sms-gateway/users/cache.go(1 hunks)internal/sms-gateway/users/domain.go(1 hunks)internal/sms-gateway/users/errors.go(1 hunks)internal/sms-gateway/users/models.go(1 hunks)internal/sms-gateway/users/module.go(1 hunks)internal/sms-gateway/users/passwords.go(1 hunks)internal/sms-gateway/users/repository.go(1 hunks)internal/sms-gateway/users/service.go(1 hunks)pkg/health/health.go(1 hunks)
💤 Files with no reviewable changes (4)
- internal/sms-gateway/modules/auth/repository.go
- internal/sms-gateway/modules/auth/cache.go
- internal/sms-gateway/models/models.go
- internal/sms-gateway/modules/auth/module.go
✅ Files skipped from review due to trivial changes (1)
- internal/sms-gateway/handlers/base/handler.go
🚧 Files skipped from review as they are similar to previous changes (24)
- go.mod
- internal/sms-gateway/handlers/base/handler_test.go
- internal/sms-gateway/handlers/middlewares/jwtauth/jwtauth.go
- cmd/sms-gateway/main.go
- internal/config/types.go
- internal/sms-gateway/handlers/settings/permissions.go
- internal/sms-gateway/users/module.go
- internal/sms-gateway/users/errors.go
- internal/sms-gateway/jwt/models.go
- internal/sms-gateway/modules/webhooks/models.go
- internal/sms-gateway/users/domain.go
- internal/sms-gateway/modules/messages/service.go
- internal/sms-gateway/users/cache.go
- internal/sms-gateway/jwt/module.go
- internal/sms-gateway/handlers/middlewares/permissions/permissions.go
- internal/sms-gateway/jwt/service.go
- internal/config/module.go
- internal/sms-gateway/handlers/thirdparty/module.go
- internal/sms-gateway/handlers/module.go
- internal/sms-gateway/jwt/errors.go
- internal/sms-gateway/users/repository.go
- deployments/prometheus/alerts/jwt-alerts.yml
- .golangci.yml
- internal/sms-gateway/models/migration.go
🧰 Additional context used
🧬 Code graph analysis (10)
internal/sms-gateway/handlers/thirdparty/auth.go (5)
internal/sms-gateway/handlers/base/handler.go (1)
Handler(15-18)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/jwt/errors.go (3)
ErrInvalidParams(9-9)ErrInitFailed(7-7)ErrDisabled(6-6)
internal/sms-gateway/users/service.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/users/errors.go (1)
ErrExists(7-7)internal/sms-gateway/users/passwords.go (2)
MakeBCryptHash(14-20)CompareBCryptHash(22-27)pkg/cache/errors.go (1)
ErrKeyNotFound(19-19)
internal/sms-gateway/handlers/devices/3rdparty.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/handlers/messages/3rdparty.go (4)
internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
RequireScope(28-36)internal/sms-gateway/handlers/messages/permissions.go (4)
ScopeList(10-10)ScopeSend(6-6)ScopeRead(8-8)ScopeExport(12-12)internal/sms-gateway/handlers/middlewares/userauth/userauth.go (1)
WithUser(132-141)
internal/sms-gateway/jwt/config.go (1)
internal/sms-gateway/jwt/errors.go (1)
ErrInvalidConfig(8-8)
internal/sms-gateway/jwt/disabled.go (2)
internal/sms-gateway/jwt/jwt.go (3)
Service(10-14)TokenInfo(23-27)Claims(16-21)internal/sms-gateway/jwt/errors.go (1)
ErrDisabled(6-6)
internal/sms-gateway/handlers/middlewares/userauth/userauth.go (3)
internal/sms-gateway/users/service.go (1)
Service(12-18)internal/sms-gateway/handlers/middlewares/permissions/permissions.go (1)
SetScopes(15-17)internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/config/config.go (2)
internal/config/types.go (1)
Duration(11-11)internal/worker/config/types.go (1)
Duration(11-11)
internal/sms-gateway/modules/settings/models.go (1)
internal/sms-gateway/users/domain.go (1)
User(5-10)
internal/sms-gateway/modules/auth/service.go (3)
internal/sms-gateway/users/service.go (1)
Service(12-18)internal/sms-gateway/users/domain.go (1)
User(5-10)internal/sms-gateway/models/models.go (2)
Device(18-29)NewDevice(31-37)
🪛 Gitleaks (8.30.0)
api/mobile.http
[high] 71-71: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 72-72: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Benchmark
- GitHub Check: Lint
- GitHub Check: E2E
- GitHub Check: Analyze (go)
🔇 Additional comments (50)
pkg/health/health.go (1)
37-42: Memory usage conversion to MiB is correct and safeUsing
m.Alloc / oneMiBbefore casting tointkeeps units consistent with"MiB"and avoids any realistic overflow concerns on supported architectures; the change looks good and the removal of the nolint is justified.internal/sms-gateway/users/passwords.go (1)
1-27: Package rename and bcrypt helpers look correct
package usersmatches the new module location and the bcrypt helpers are straightforward, with appropriate error wrapping viaErrPasswordInvalid. I don’t see any functional or security regressions introduced by this move.internal/sms-gateway/users/models.go (2)
17-23: Constructor is minimal and appropriate for an internal ORM model
newUserModelcleanly initializes only the explicit fields while relying on the embeddedSoftDeletableModeldefaults, and the//nolint:exhaustructis an appropriate suppression here. No issues from a correctness or clarity standpoint.
25-34: TableName override and Migrate wiring look consistent with GORM usageOverriding
TableName()to"users"aligns the ORM model with the expected table name, andMigrateusingdb.AutoMigrate(new(userModel))is idiomatic and keeps the migration surface narrow. Just make sure this function is wired into your global migrations alongside other modules so theuserstable is guaranteed to exist before use.internal/sms-gateway/modules/settings/models.go (2)
7-7: LGTM! Import aligns with type migration.The users package import is necessary for the
users.Usertype reference on line 17.
17-17: LGTM! Foreign key relationship preserved.The type migration from
models.Usertousers.Usermaintains the foreign key constraint and cascade behavior. The relationship should function correctly as long asusers.Userfollows GORM conventions withIDas the primary key.internal/sms-gateway/handlers/settings/3rdparty.go (5)
8-8: LGTM! Imports align with JWT authentication integration.The permissions and users package imports support the scope-based access control and type migration introduced in this file.
Also applies to: 12-12
50-50: LGTM! OpenAPI annotations reflect JWT authentication support.The addition of
JWTAuthsecurity and 403 Forbidden response aligns with the scope-based permission checks introduced in the route registration.Also applies to: 55-55
60-60: LGTM! Handler signature updated for type migration.The signature change from
models.Usertousers.Useris consistent with the broader user domain refactoring.
72-72: LGTM! Consistent JWT authentication integration across all handlers.The OpenAPI annotations and signature updates for
putandpatchhandlers mirror thegethandler changes, maintaining consistency across the API.Also applies to: 80-80, 85-85, 108-108, 116-116, 121-121
141-143: Scope constants are properly defined and correctly used.The
ScopeReadandScopeWriteconstants are defined inpermissions.gowith appropriate values ("settings:read"and"settings:write"), and the routes correctly apply them viapermissions.RequireScope(). The middleware order (permission check before user extraction) is correct.internal/sms-gateway/handlers/logs/permissions.go (1)
1-5: LGTM!Clean permission constant definition following the established naming pattern used by other handler modules (e.g.,
devices:list,messages:send).internal/sms-gateway/jwt/disabled.go (1)
8-28: LGTM!Well-implemented Null Object pattern for the disabled JWT service. All three interface methods correctly return
ErrDisabled, providing a clean fallback when JWT is not configured.api/requests.http (1)
200-223: Token endpoints structure looks good.The new
/auth/tokenPOST and DELETE endpoints are properly structured with correct authentication headers. The token generation request body withttlandscopesarray aligns with the JWT service interface.deployments/grafana/dashboards/jwt.json (1)
1-1217: Dashboard metrics and structure look correct.The dashboard provides comprehensive JWT observability with proper metric names matching those defined in
internal/sms-gateway/jwt/metrics.go. The panels cover issuance, validation, and revocation rates/latencies appropriately.internal/sms-gateway/jwt/config.go (2)
8-10: LGTM on secret length requirement.The 32-byte minimum for HMAC-SHA256 secrets aligns with security best practices (256-bit key strength).
18-31: Add validation for the Issuer field.The
Issuerfield is used directly in token creation (line 86) and token validation (line 132) ofservice.go, but theValidate()method does not check that it is set. Add a validation check to ensureIssueris not empty.internal/sms-gateway/jwt/jwt.go (2)
10-14: LGTM!Clean service interface with appropriate method signatures. The context parameter enables cancellation and deadline propagation for token operations.
16-21: LGTM!Standard JWT claims extension pattern with proper JSON tags for the custom fields.
internal/sms-gateway/handlers/logs/3rdparty.go (1)
57-58: Middleware ordering is correct.The
permissions.RequireScope(ScopeRead)middleware can access scopes because the global middleware chain inhandlers/3rdparty.goregistersjwtauth.NewJWT()before any route handlers run. The JWT middleware extracts scopes from token claims and populates them in the context viapermissions.SetScopes()at the global level, making them available to all downstream route-level middleware. The route-level ordering aligns with the codebase pattern used consistently across all third-party handlers.internal/config/config.go (1)
87-91: LGTM: JWT configuration structure is well-defined.The JWT type is properly structured with appropriate YAML and environment variable tags. The use of the Duration type for TTL aligns with the existing codebase patterns (as seen in internal/config/types.go).
configs/config.example.yml (1)
41-44: LGTM: Clear example configuration with helpful guidance.The JWT configuration block is well-documented with environment variable references and a clear comment explaining that an empty secret disables JWT functionality.
internal/sms-gateway/jwt/metrics.go (3)
8-21: LGTM: Metric constants and labels are well-defined.The metric naming follows Prometheus conventions, and the status label provides adequate granularity for success/error tracking. The bucket configuration for histograms (line 35) covers a good range for JWT operation latencies.
34-82: LGTM: Metrics initialization and registration.The metrics are properly registered with promauto, ensuring they're available immediately. The use of CounterVec for token counts and Histogram for duration tracking is appropriate for JWT operations.
84-118: LGTM: Clean metric recording methods.The increment and observe methods provide a clean interface for recording metrics. The timer pattern in the Observe methods correctly captures operation duration.
internal/sms-gateway/app.go (1)
10-10: LGTM: JWT and users modules properly integrated.The module registration follows the established fx pattern and is correctly positioned in the dependency graph.
Also applies to: 24-24, 45-45, 60-60
internal/sms-gateway/handlers/thirdparty/permissions.go (1)
1-5: LGTM: Scope constant follows naming convention.The
ScopeTokensManageconstant follows the established pattern for permission scopes seen in other handlers (e.g.,ScopeList,ScopeDeletein devices/permissions.go).internal/sms-gateway/handlers/devices/3rdparty.go (2)
42-42: LGTM: OpenAPI annotations updated for JWT authentication.The JWTAuth security scheme and 403 Forbidden responses are properly added to reflect the new permission-based authorization.
Also applies to: 48-48, 67-67, 74-74
53-53: LGTM: Parameter types updated for new users domain.The migration from
models.Usertousers.Useris consistent with the broader refactoring to introduce a dedicated users package.Also applies to: 80-80
internal/sms-gateway/handlers/messages/permissions.go (1)
1-13: LGTM!Well-documented permission scope constants with clear naming conventions. This serves as a good reference for other permission files in the codebase.
internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql (1)
3-13: Schema design looks appropriate for JWT token persistence.The table structure correctly supports the JWT revocation and expiry use cases with appropriate indexes and foreign key constraints. The
ON DELETE CASCADEensures orphaned tokens are cleaned up when users are deleted.internal/sms-gateway/handlers/webhooks/permissions.go (1)
1-7: LGTM with minor suggestion.Scope constants follow consistent naming conventions. Consider adding documentation comments for consistency with
messages/permissions.go, similar to the suggestion fordevices/permissions.go.internal/sms-gateway/handlers/thirdparty/auth.go (2)
107-130: Error handler implementation looks correct.The error handler appropriately maps JWT-specific errors to meaningful HTTP responses. The
ErrInvalidConfigandErrInitFailedcases correctly return 500 with a user-friendly message, andErrDisabledreturns 501 Not Implemented which is appropriate for a feature toggle.
19-36: Handler structure and constructor look good.Clean composition with embedded
base.Handlerand injectedjwt.Service. Constructor properly initializes both.internal/sms-gateway/handlers/webhooks/3rdparty.go (1)
123-127: LGTM! Routes properly wrapped with scope-based permissions.The route registration correctly applies
permissions.RequireScopebeforeuserauth.WithUser, ensuring scope validation occurs before handler execution. The pattern is consistent with the broader JWT authentication flow.internal/sms-gateway/openapi/docs.go (1)
1-4: Generated file correctly reflects JWT authentication additions.The OpenAPI spec properly documents the new
/3rdparty/v1/auth/tokenendpoints,JWTAuthsecurity definition, and 403 Forbidden responses across all protected endpoints. Previous review concerns (security annotations, TTL type, jti parameter) have been addressed.internal/sms-gateway/jwt/repository.go (1)
38-47: Verify behavior for non-existent tokens.
IsRevokedreturnsfalsefor tokens that don't exist in the database (count would be 0). Ensure the JWT validation flow elsewhere verifies token existence or that this behavior is intentional (e.g., tokens not persisted until revoked).internal/sms-gateway/handlers/middlewares/userauth/userauth.go (2)
54-55: LGTM! Basic auth correctly grants full scope access.The pattern of granting
permissions.ScopeAllafter successful Basic authentication is appropriate, as Basic auth represents full user credentials. The user storage viaSetUseris consistent with theNewCodepath.
132-140: LGTM! WithUser now returns proper 401 instead of panicking.The previous concern about potential panics when
GetUserreturns nil has been addressed. The middleware now correctly returnsfiber.ErrUnauthorizedfor unauthenticated requests.internal/sms-gateway/handlers/messages/3rdparty.go (1)
76-172: LGTM! Handler properly updated for JWT auth integration.The
posthandler correctly usesusers.Userand the Swagger annotations properly documentJWTAuthsecurity and 403 Forbidden response. The permission scopeScopeSendis appropriately applied at the route level.internal/sms-gateway/handlers/3rdparty.go (2)
20-33: LGTM on struct updates for JWT integration.The new fields (
usersSvc,jwtSvc,authHandler) align with the JWT authentication architecture. The organization keeps auth-related dependencies together.
74-80: Auth middleware stack and routing look correct.The middleware chain correctly supports dual authentication (Basic + JWT) with
UserRequired()enforcing that one must succeed. Auth routes being behind authentication is appropriate for token refresh/revocation operations where an authenticated session is required.internal/sms-gateway/modules/auth/service.go (8)
23-33: Service struct refactoring is clean.The replacement of inline repository with
*users.Serviceimproves separation of concerns and aligns with the broader refactoring to use the public users service.
35-53: Constructor signature is well-structured.Dependencies are clearly defined and the initialization is straightforward. The
codesCacheinitialization withcodeTTLmaintains the one-time code functionality.
56-83: Code generation logic is sound with appropriate retry handling.The 3-retry loop with cryptographic random generation and cache collision handling is well-designed. The 6-digit code format is reasonable for one-time codes.
85-96: RegisterDevice correctly uses value type for User.Since only
user.IDis needed, passingusers.Userby value is appropriate and avoids nil pointer concerns.
102-112: Good use of constant-time comparison for token validation.Using
subtle.ConstantTimeComparefor the private token check prevents timing attacks. This is a security best practice.
133-145: AuthorizeUserByCode refactored to use users.Service.The function correctly retrieves the user ID from the codes cache and delegates to the users service. Error handling is appropriate with wrapped errors.
147-165: Cleanup logic is straightforward.The hourly cleanup ticker for
codesCachemaintains proper cache hygiene. The context cancellation handling is correct.
120-125: Fire-and-forget goroutine is appropriate for this use case.The detached goroutine with its own timeout context is well-suited for non-critical online status updates. The
SetOnlinemethod returns void, so there are no errors to log. No changes needed.
internal/sms-gateway/models/migrations/mysql/20251121071748_add_tokens.sql
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
internal/sms-gateway/handlers/thirdparty/auth.go (1)
66-74: Validate TTL bounds before conversion to prevent overflow.Although a previous review comment indicated this was addressed, the handler still converts
req.TTLdirectly totime.Durationwithout validation. Ifreq.TTLexceedsmath.MaxInt64 / int64(time.Second)(~292 years), the multiplication will overflow, potentially producing negative or incorrect durations.Add validation before the conversion to ensure
req.TTLis within acceptable bounds (e.g., 0 to 31,536,000 seconds for one year).Apply this diff to add validation:
+ const maxTTLSeconds = 31536000 // 1 year + if req.TTL > maxTTLSeconds { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("ttl exceeds maximum allowed value of %d seconds", maxTTLSeconds)) + } + token, err := h.jwtSvc.GenerateToken( c.Context(), user.ID, req.Scopes, time.Duration(req.TTL)*time.Second, )
🧹 Nitpick comments (1)
internal/sms-gateway/handlers/thirdparty/auth.go (1)
98-106: Add validation for the jti parameter.The
jtiparameter is extracted from the path but not validated before being passed toRevokeToken. Although the router should ensure the parameter exists, adding a defensive check improves error handling and provides a clearer error message if the parameter is somehow empty.Apply this diff to add validation:
func (h *AuthHandler) deleteToken(user users.User, c *fiber.Ctx) error { jti := c.Params("jti") + if jti == "" { + return fiber.NewError(fiber.StatusBadRequest, "jti parameter is required") + } if err := h.jwtSvc.RevokeToken(c.Context(), user.ID, jti); err != nil { return fmt.Errorf("failed to revoke token: %w", err) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
cmd/sms-gateway/main.go(2 hunks)internal/sms-gateway/handlers/thirdparty/auth.go(1 hunks)internal/sms-gateway/openapi/docs.go(31 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- cmd/sms-gateway/main.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E
- GitHub Check: Benchmark
- GitHub Check: Lint
- GitHub Check: Analyze (go)
🔇 Additional comments (3)
internal/sms-gateway/handlers/thirdparty/auth.go (2)
25-42: LGTM!The constructor and route registration are well-structured. The middleware ordering is correct, with
errorHandlerregistered first to catch errors from downstream handlers, and both endpoints properly protected with scope and authentication requirements.
108-131: LGTM!The error handler properly maps JWT service errors to appropriate HTTP responses with clear user-facing messages. The
nolint:wrapcheckdirective is correctly justified since Fiber's error handling mechanism requires unwrapped errors.internal/sms-gateway/openapi/docs.go (1)
1-1999: Generated documentation looks correct.This file is auto-generated by swaggo/swag (as noted in the header comment). The generated OpenAPI specification correctly reflects:
- JWT authentication endpoints with proper security requirements (lines 26-145)
- Complete parameter definitions including the
jtipath parameter (lines 112-119)- Appropriate schema definitions for
TokenRequestandTokenResponse(lines 1840-1879)- JWTAuth security definition (lines 1960-1965)
- Updated security requirements across existing endpoints
All previously flagged documentation issues appear to be resolved in the generated output.
Summary by CodeRabbit
New Features
Configuration
Security
Observability
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.