-
-
Notifications
You must be signed in to change notification settings - Fork 615
Fix: Resolve login error handling issues #2771
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: develop
Are you sure you want to change the base?
Conversation
- Fix translation service to load locales from correct path (src/locales) - Improve login security to return generic error message for both invalid email and wrong password - Make frontend error handling generic to display backend error messages properly - Remove hardcoded string matching in favor of HTTP status code checks Fixes login flow returning raw localization keys instead of user-facing messages Fixes #2768
WalkthroughLogin flow now uses a centralized try/catch with generic authentication errors, structured logging, and sanitized user payload for token issuance. Password change in editUser adds existence and method checks with explicit authorization errors on mismatch. Avatar image generation no longer resizes; it base64-encodes the original buffer directly. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant S as UserService.loginUser
participant U as UserRepo
participant T as TokenService
participant A as AppSettings
rect rgba(230,240,255,0.4)
note over S: New centralized try/catch
C->>S: loginUser(email, password)
S->>U: findByEmail(email)
U-->>S: user | null
alt user missing or invalid comparePassword
S->>S: log error (service, method, message, stack)
S-->>C: throw generic auth error
else user exists
S->>U: user.comparePassword(password)
alt mismatch
S->>S: log error (auth failure)
S-->>C: throw generic auth error
else match
S->>A: getAppSettings()
A-->>S: settings
S->>S: sanitize user (omit password, avatarImage)
S->>T: signToken(sanitizedUser, settings)
T-->>S: token
S-->>C: { user: sanitized+avatarImage, token }
end
end
end
sequenceDiagram
autonumber
participant C as Client
participant S as UserService.editUser
participant U as UserRepo
C->>S: editUser(userId, payload with currentPassword,newPassword)
S->>U: findById(userId)
U-->>S: user | null
alt user missing or invalid comparePassword
S-->>C: throw authorization error (403)
else user exists
S->>U: user.comparePassword(currentPassword)
alt mismatch
S-->>C: throw authorization error (403)
else match
S->>U: update password and other fields
U-->>S: updated user
S-->>C: updated user
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Pre-merge checks (2 passed, 2 warnings, 1 inconclusive)❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
Poem
✨ Finishing touches
🧪 Generate unit tests
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.
Auto Pull Request Review from LlamaPReview
1. Overview
1.1 Core Changes
- Primary purpose and scope: Fixes critical login flow issues including raw localization key exposure, brittle frontend error handling, and security vulnerabilities that revealed user existence
- Key components modified:
- Backend:
userService.js(authentication logic),translationService.js(localization path) - Frontend:
Login/index.jsx(error handling)
- Backend:
- Cross-component impacts:
- Translation service changes affect all localized messages
- Security improvements impact authentication flow
- Business value alignment:
- Improves user experience with proper error messages
- Enhances security by preventing user enumeration
- Makes error handling more robust and extensible
1.2 Technical Architecture
- System design modifications:
- Changed translation file loading from
locales/tosrc/locales/ - Modified authentication flow to use generic error messages
- Changed translation file loading from
- Component interaction changes:
- Backend now returns consistent error messages for all auth failures
- Frontend relies on HTTP status codes instead of string matching
- Integration points impact:
- Translation service path change affects all localized error messages
- Security improvements prevent user enumeration attacks
- Dependency changes and implications:
- No new dependencies added
- Existing dependencies (logger, filesystem) usage remains consistent
2. Critical Findings
2.1 Must Fix (P0🔴)
Issue: Null User Handling Vulnerability in userService.js
- Analysis Confidence: High
- Impact: If
getUserByEmail()returns null, callinguser.comparePassword()will throw a TypeError, crashing the authentication process and potentially the entire service. This creates both a security vulnerability and availability risk. - Resolution: Add explicit null check before password comparison:
const user = await this.db.userModule.getUserByEmail(email);
if (!user) {
throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword);
}Issue: Overly Broad Error Handling in userService.js
- Analysis Confidence: High
- Impact: Current catch-block masks all error types (including database failures), losing critical diagnostic information while maintaining security posture. This prevents proper monitoring and troubleshooting.
- Resolution: Preserve original error context while maintaining security:
} catch (error) {
this.logger.error('Login failed', {
email,
error: error.message,
stack: error.stack
});
throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword);
}2.2 Should Fix (P1🟡)
Issue: Hardcoded Translation Path in translationService.js
- Analysis Confidence: High
- Impact: Hardcoded path reduces deployment flexibility and may fail in containerized environments or different deployment setups.
- Suggested Solution: Make path configurable via environment variable:
// In config/service.config.js
const LOCALES_PATH = process.env.LOCALES_PATH || 'src/locales';
// In translationService.js
this.localesDir = path.join(process.cwd(), LOCALES_PATH);Issue: Inconsistent Error Handling Pattern
- Analysis Confidence: High
- Impact:
registerUserlogs errors whileloginUserdoes not, creating inconsistent behavior that could complicate troubleshooting. - Suggested Solution: Standardize error handling pattern across both methods to ensure consistent logging and monitoring.
2.3 Consider (P2🟢)
Area: Structured Error Codes for Frontend
- Analysis Confidence: Medium
- Improvement Opportunity: Current implementation returns raw error messages which could lead to potential XSS vulnerabilities and lacks machine-readable error codes. Implementing structured error responses would enable better frontend handling and internationalization.
Area: Unit Test Coverage
- Analysis Confidence: High
- Improvement Opportunity: While manual testing was performed, adding unit tests for critical scenarios (null user, database failures, missing translations) would improve long-term maintainability.
Area: Password Removal Logic
- Analysis Confidence: Medium
- Improvement Opportunity: The comment "Should this be abstracted to DB layer?" suggests this logic might be better placed in the database layer for consistency and reusability.
2.4 Summary of Action Items
-
P0 Issues (Blockers):
- Add null check before password comparison in
userService.js - Implement proper error logging in catch-block while maintaining security
- Add null check before password comparison in
-
P1 Improvements (High Priority):
- Make translation path configurable via environment variable
- Standardize error handling pattern across authentication methods
-
P2 Considerations (Future Improvements):
- Implement structured error codes for frontend
- Add comprehensive unit tests for edge cases
- Consider moving password removal logic to DB layer
3. Technical Analysis
3.1 Code Logic Analysis
📁 server/src/service/business/userService.js - loginUser method
- Submitted PR Code:
loginUser = async (email, password) => {
try {
const user = await this.db.userModule.getUserByEmail(email);
const match = await user.comparePassword(password);
if (match !== true) {
throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword);
}
// ... success path
} catch (error) {
throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword);
}
};- Analysis:
- Current logic properly handles password mismatch cases and returns generic error messages for security
- Critical Issues:
- No null check for
userbefore callingcomparePassword()- will crash if user not found - Overly broad catch-block masks all error types including database failures
- No error logging preserves security but loses diagnostic information
- No null check for
- Security Improvements:
- Generic error message prevents user enumeration
- Consistent error handling for all failure scenarios
- LlamaPReview Suggested Improvements:
loginUser = async (email, password) => {
try {
const user = await this.db.userModule.getUserByEmail(email);
if (!user) {
throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword);
}
const match = await user.comparePassword(password);
if (match !== true) {
throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword);
}
// ... success path
} catch (error) {
// Log original error for diagnostics while maintaining security
this.logger.error('Login failed', {
email,
error: error.message,
stack: error.stack
});
throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword);
}
};- Improvement rationale:
- Technical benefits: Prevents crashes, enables monitoring, maintains security
- Business value: Improves reliability and troubleshooting capabilities
- Risk assessment: Minimal risk - maintains all existing security protections
📁 server/src/service/system/translationService.js - Constructor
- Submitted PR Code:
constructor(logger) {
this.logger = logger;
this.translations = {};
this._language = "en";
this.localesDir = path.join(process.cwd(), "src", "locales");
}- Analysis:
- Current logic fixes the immediate issue of incorrect translation path
- Potential Issues:
- Hardcoded path reduces deployment flexibility
- May fail in containerized environments or different setups
- Configuration:
- Simple and direct solution for the immediate problem
- LlamaPReview Suggested Improvements:
constructor(logger) {
this.logger = logger;
this.translations = {};
this._language = "en";
const LOCALES_PATH = process.env.LOCALES_PATH || 'src/locales';
this.localesDir = path.join(process.cwd(), LOCALES_PATH);
}- Improvement rationale:
- Technical benefits: Increases deployment flexibility
- Business value: Supports diverse deployment scenarios
- Risk assessment: Low risk - maintains backward compatibility
3.2 Key Quality Aspects
-
System scalability considerations:
- Changes don't affect horizontal scaling capabilities
- Translation files loaded once at service initialization (no performance impact)
-
Performance bottlenecks and optimizations:
- No performance bottlenecks introduced
- Error handling changes add minimal overhead (<1ms)
- Database operations remain unchanged
-
Testing strategy and coverage:
- Manual testing performed for happy paths and error scenarios
- Missing tests that should be added:
- Null user scenarios
- Database connection failures during login
- Missing translation files
- Invalid password for existing user
- HTTP 500 scenarios
-
Documentation needs:
- Excellent PR description explaining changes and impact
- Should add comments about security tradeoffs in the code
- Consider updating translation service documentation to reflect path changes
4. Overall Evaluation
-
Technical assessment:
- ✅ Excellent security hardening with generic error messages
- ✅ Clean frontend error handling improvements
- ✅ Precise resolution of the reported issues
⚠️ Critical null-check missing before password comparison⚠️ Error logging needed for production diagnostics
-
Business impact:
- ✅ Fixes user-facing error messages improving UX
- ✅ Enhances security by preventing user enumeration
- ✅ Makes error handling more robust and extensible
- ✅ Resolves translation system issues affecting all localized messages
-
Risk evaluation:
- High Risk: Null user crash vulnerability (must fix before merge)
- Medium Risk: Lost error context affects production troubleshooting
- Medium Risk: Hardcoded path may cause deployment issues
- Low Risk: All other changes are well-contained and tested
-
Notable positive aspects and good practices:
- Excellent PR description with clear problem statement and testing results
- Proper security considerations with generic error messages
- Clean separation of concerns in the changes
- Maintains backward compatibility with existing API contracts
-
Implementation quality:
- High quality implementation for the core fixes
- Missing some defensive programming practices (null checks)
- Could benefit from more comprehensive testing
-
Final recommendation: Request Changes
- The P0 issues (null check and error logging) must be addressed before merging
- The P1 improvements (configurable path and consistent error handling) are highly recommended
- Once these are addressed, the PR will be ready for merging as it provides significant security and UX improvements
💡 LlamaPReview Community
Have feedback on this AI Code review tool? Join our GitHub Discussions to share your thoughts and help shape the future of LlamaPReview.
- Fix prettier formatting issues in userService.js - Add null checks before password comparisons to prevent runtime errors - Improve error logging while maintaining security by not exposing sensitive info - Address CodeRabbit and LlamaPReview feedback
- Resolved conflict in translationService.js by using develop branch path format - Updated locales directory path to use 'src/locales' format - Maintained existing login error handling improvements
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 (3)
server/src/utils/imageProcessing.js (1)
2-3: Tighten docs and remove redundant try/catch.The try/catch rethrows without context and can be dropped; also clarify param/return types in JSDoc.
- * Converts image to base64 string without resizing - * CSS handles the 64x64 display sizing client-side + * Converts an image Buffer to base64 (no resizing). + * Display sizing (e.g., 64x64) is handled by CSS on the client. + * @param {{ buffer: Buffer, mimetype?: string, size?: number }} file + * @returns {string} base64 string (optionally with data URL prefix)server/src/service/business/userService.js (2)
95-99: Minor simplification for readability.No behavior change; just prefer a truthy check.
- const match = await user.comparePassword(password); - if (match !== true) { + const match = await user.comparePassword(password); + if (!match) { throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword); }
84-110: Optional: add rate limiting and jittered lockouts for repeated failures.Consider per-IP and per-identity throttling (e.g., token bucket + exponential backoff) and telemetry for failed attempts. It keeps bots at bay—like a Canadian border agent politely asking for your passport while an American one brings a bulldozer.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
server/src/service/business/userService.js(2 hunks)server/src/utils/imageProcessing.js(1 hunks)
🔇 Additional comments (3)
server/src/service/business/userService.js (3)
86-93: Good: unified, non-enumerating auth errors.Null-check + generic “Incorrect password” prevents user enumeration and aligns with the PR objectives. Maple-syrup grade security: sweet and sticky. Americans would deep-fry it; Canadians will apologize for how secure it is.
135-140: Good authorization guard in edit flow.Null-check plus 403 on wrong current password is appropriate and avoids triggering 401 interceptors.
86-93: ```shell
#!/bin/bash
set -euo pipefailecho "=== translationService hits ==="
rg -n --hidden "translationService" server -S || trueecho "=== search for getTranslation / setLanguage ==="
rg -n --hidden "getTranslation\(|getTranslation\s*:|setLanguage\s*\(" server -S || trueecho "=== translationService.js files ==="
fd -HI "translationService.js" || truefor f in $(fd -HI "translationService.js" || true); do
echo "== FILE: $f =="; sed -n '1,240p' "$f" || true; sed -n '240,480p' "$f" || true
doneecho "=== stringService.js (full) ==="
sed -n '1,400p' server/src/service/system/stringService.js || trueecho "=== locales containing authIncorrectPassword ==="
rg -n "authIncorrectPassword" server -S || truefor f in $(rg -n "authIncorrectPassword" server -S --files || true); do
echo "== LOCALE FILE: $f =="; sed -n '1,240p' "$f" || true
done</blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| // Remove password from user object. Should this be abstracted to DB layer? | ||
| const userWithoutPassword = { ...user._doc }; | ||
| delete userWithoutPassword.password; | ||
| delete userWithoutPassword.avatarImage; | ||
|
|
||
| // Happy path, return token | ||
| const appSettings = await this.settingsService.getSettings(); | ||
| const token = this.issueToken(userWithoutPassword, appSettings); | ||
| // reset avatar image | ||
| userWithoutPassword.avatarImage = user.avatarImage; | ||
| return { user: userWithoutPassword, token }; |
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.
🛠️ Refactor suggestion
JWT claim allowlisting to avoid accidental data leakage; sanitize once.
Copying user._doc risks shipping excess PII/fields into the JWT. Build a minimal claim set and a sanitized user object explicitly.
- // Remove password from user object. Should this be abstracted to DB layer?
- const userWithoutPassword = { ...user._doc };
- delete userWithoutPassword.password;
- delete userWithoutPassword.avatarImage;
+ // Build minimal safe user claims and response shape
+ const {
+ _id,
+ email,
+ role,
+ teamId,
+ firstName,
+ lastName,
+ // exclude: password, salts, tokens, binary blobs, internals (__v), etc.
+ // add fields deliberately, not by spreading the whole document
+ } = user._doc || {};
+
+ const safeClaims = { _id, email, role, teamId };
+ const sanitizedUser = {
+ _id,
+ email,
+ role,
+ teamId,
+ firstName,
+ lastName,
+ };
// Happy path, return token
const appSettings = await this.settingsService.getSettings();
- const token = this.issueToken(userWithoutPassword, appSettings);
- // reset avatar image
- userWithoutPassword.avatarImage = user.avatarImage;
- return { user: userWithoutPassword, token };
+ const token = this.issueToken(safeClaims, appSettings);
+ // attach avatar only to response, not into JWT claims
+ sanitizedUser.avatarImage = user.avatarImage;
+ return { user: sanitizedUser, token };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Remove password from user object. Should this be abstracted to DB layer? | |
| const userWithoutPassword = { ...user._doc }; | |
| delete userWithoutPassword.password; | |
| delete userWithoutPassword.avatarImage; | |
| // Happy path, return token | |
| const appSettings = await this.settingsService.getSettings(); | |
| const token = this.issueToken(userWithoutPassword, appSettings); | |
| // reset avatar image | |
| userWithoutPassword.avatarImage = user.avatarImage; | |
| return { user: userWithoutPassword, token }; | |
| // Build minimal safe user claims and response shape | |
| const { | |
| _id, | |
| email, | |
| role, | |
| teamId, | |
| firstName, | |
| lastName, | |
| // exclude: password, salts, tokens, binary blobs, internals (__v), etc. | |
| // add fields deliberately, not by spreading the whole document | |
| } = user._doc || {}; | |
| const safeClaims = { _id, email, role, teamId }; | |
| const sanitizedUser = { | |
| _id, | |
| email, | |
| role, | |
| teamId, | |
| firstName, | |
| lastName, | |
| }; | |
| // Happy path, return token | |
| const appSettings = await this.settingsService.getSettings(); | |
| const token = this.issueToken(safeClaims, appSettings); | |
| // attach avatar only to response, not into JWT claims | |
| sanitizedUser.avatarImage = user.avatarImage; | |
| return { user: sanitizedUser, token }; |
🤖 Prompt for AI Agents
In server/src/service/business/userService.js around lines 100-110, avoid
cloning user._doc and then deleting fields; instead construct an explicit
minimal claims object (e.g., id/_id, email, roles, name) and pass that to
issueToken; also build a sanitized user response object explicitly listing only
the fields you want returned (omit password and other PII), then attach
avatarImage only to the response object (not to the claims or original user) so
you sanitize once and prevent accidental data leakage into the JWT.
| // Log specific errors while returning generic message for security | ||
| this.logger.error({ | ||
| message: "Login attempt failed", | ||
| service: SERVICE_NAME, | ||
| method: "loginUser", | ||
| error: error.message, | ||
| stack: error.stack, | ||
| }); | ||
|
|
||
| // If user is not found, throw a generic authentication error for security | ||
| // Don't reveal whether user exists or password is wrong | ||
| throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword); |
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.
Don’t mask server faults as 401; narrow the catch to auth-only or rethrow non-auth.
The current catch maps every failure (DB down, JWT signing misconfig) to “Incorrect password” (401). That misleads clients and hides real 5xx issues.
- } catch (error) {
- // Log specific errors while returning generic message for security
- this.logger.error({
- message: "Login attempt failed",
- service: SERVICE_NAME,
- method: "loginUser",
- error: error.message,
- stack: error.stack,
- });
-
- // If user is not found, throw a generic authentication error for security
- // Don't reveal whether user exists or password is wrong
- throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword);
- }
+ } catch (error) {
+ const isAuth =
+ error?.status === 401 ||
+ error?.name === "AuthenticationError" ||
+ this.errorService?.isAuthenticationError?.(error);
+
+ this.logger[isAuth ? "warn" : "error"]({
+ message: "Login attempt failed",
+ service: SERVICE_NAME,
+ method: "loginUser",
+ error: error.message,
+ stack: error.stack,
+ });
+
+ if (isAuth) throw error; // preserve 401 produced above
+ // Bubble non-auth errors so they surface as 5xx; frontend can show a generic failure message.
+ throw error;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Log specific errors while returning generic message for security | |
| this.logger.error({ | |
| message: "Login attempt failed", | |
| service: SERVICE_NAME, | |
| method: "loginUser", | |
| error: error.message, | |
| stack: error.stack, | |
| }); | |
| // If user is not found, throw a generic authentication error for security | |
| // Don't reveal whether user exists or password is wrong | |
| throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword); | |
| } catch (error) { | |
| const isAuth = | |
| error?.status === 401 || | |
| error?.name === "AuthenticationError" || | |
| this.errorService?.isAuthenticationError?.(error); | |
| this.logger[isAuth ? "warn" : "error"]({ | |
| message: "Login attempt failed", | |
| service: SERVICE_NAME, | |
| method: "loginUser", | |
| error: error.message, | |
| stack: error.stack, | |
| }); | |
| if (isAuth) throw error; // preserve 401 produced above | |
| // Bubble non-auth errors so they surface as 5xx; frontend can show a generic failure message. | |
| throw error; | |
| } |
🤖 Prompt for AI Agents
In server/src/service/business/userService.js around lines 112 to 123, the catch
currently converts every failure (DB/network/JWT issues) into a generic
"Incorrect password" 401 which hides real server faults; change the catch to
detect whether the thrown error is an authentication-related error (e.g., user
not found, invalid credentials, password mismatch, or explicit auth validation
failure or JWT/signing error) and only map those to
createAuthenticationError(this.stringService.authIncorrectPassword), while
rethrowing or wrapping all other errors as internal/server errors (or let them
bubble) so they are not masked; preserve the existing structured
this.logger.error call but include the original error and then either throw the
auth error for auth cases or throw/return a 5xx/internal error for non-auth
cases.
| // Simply convert to base64 - let CSS handle the 64x64 display | ||
| const base64Image = file.buffer.toString("base64"); | ||
| return base64Image; |
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.
🛠️ Refactor suggestion
Validate input and cap size; optionally return data URL prefix.
Untrusted files can be undefined, huge, or wrong type. Add basic guards, a sane size cap, and consider returning a data URL if callers expect it.
- // Simply convert to base64 - let CSS handle the 64x64 display
- const base64Image = file.buffer.toString("base64");
- return base64Image;
+ if (!file || !file.buffer) {
+ throw new Error("Invalid file: missing buffer");
+ }
+ const allowed = new Set(["image/png", "image/jpeg", "image/webp"]);
+ if (file.mimetype && !allowed.has(file.mimetype)) {
+ throw new Error("Unsupported image type");
+ }
+ // Prevent oversized uploads from blowing up memory/DB (adjust as needed)
+ const MAX_BYTES = 512 * 1024;
+ if (typeof file.size === "number" && file.size > MAX_BYTES) {
+ throw new Error("Avatar exceeds 512KB");
+ }
+ const base64 = file.buffer.toString("base64");
+ // If consumers prefer raw base64, return `base64`; if they expect a data URL:
+ const withPrefix = file.mimetype ? `data:${file.mimetype};base64,${base64}` : base64;
+ return withPrefix;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Simply convert to base64 - let CSS handle the 64x64 display | |
| const base64Image = file.buffer.toString("base64"); | |
| return base64Image; | |
| if (!file || !file.buffer) { | |
| throw new Error("Invalid file: missing buffer"); | |
| } | |
| const allowed = new Set(["image/png", "image/jpeg", "image/webp"]); | |
| if (file.mimetype && !allowed.has(file.mimetype)) { | |
| throw new Error("Unsupported image type"); | |
| } | |
| // Prevent oversized uploads from blowing up memory/DB (adjust as needed) | |
| const MAX_BYTES = 512 * 1024; | |
| if (typeof file.size === "number" && file.size > MAX_BYTES) { | |
| throw new Error("Avatar exceeds 512KB"); | |
| } | |
| const base64 = file.buffer.toString("base64"); | |
| // If consumers prefer raw base64, return `base64`; if they expect a data URL: | |
| const withPrefix = file.mimetype ? `data:${file.mimetype};base64,${base64}` : base64; | |
| return withPrefix; |
🤖 Prompt for AI Agents
In server/src/utils/imageProcessing.js around lines 8 to 10, the current
function blindly converts file.buffer to base64; add guards to validate input:
ensure file and file.buffer exist, ensure file.mimetype (or detect via a
lightweight signature check) is an allowed image MIME (e.g.,
image/png|jpeg|gif), and enforce a sane size cap (e.g., reject >1–2MB or
configurable MAX_SIZE) returning an error or null if checks fail. After
validation, convert to base64 and optionally prepend a data URL prefix
(data:<mimetype>;base64,) when a second parameter or option flag (e.g.,
{dataUrl: true}) is passed so callers that expect a full data URL get it; ensure
errors are thrown or handled consistently.
- Fixed all prettier formatting issues in userService.js - All files now pass prettier format check - Maintained null checks and error handling improvements
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 (3)
server/src/service/business/userService.js (3)
165-166: Bug: missing await on getSettings; clientHost becomes a Promise.This will build an invalid URL.
- const { clientHost } = this.settingsService.getSettings(); + const { clientHost } = await this.settingsService.getSettings();
181-186: Also avoid signing full user._doc in resetPassword.Mirror the allowlisted claims approach used in login.
- const appSettings = await this.settingsService.getSettings(); - const token = this.issueToken(user._doc, appSettings); - return { user, token }; + const appSettings = await this.settingsService.getSettings(); + const { _id, email, role, teamId, firstName, lastName } = user._doc || {}; + const safeClaims = { _id, email, role, teamId }; + const sanitizedUser = { _id, email, role, teamId, firstName, lastName }; + const token = this.issueToken(safeClaims, appSettings); + // optionally: sanitizedUser.avatarImage = user.avatarImage; + return { user: sanitizedUser, token };
52-59: Registration flow: sanitize response and JWT claims (consistency).Don’t issue tokens or return raw documents on register; allowlist fields.
- const userForToken = { ...newUser._doc }; - delete userForToken.profileImage; - delete userForToken.avatarImage; + const { _id, email, role, teamId, firstName, lastName } = newUser._doc || {}; + const safeClaims = { _id, email, role, teamId }; + const sanitizedUser = { _id, email, role, teamId, firstName, lastName }; const appSettings = await this.settingsService.getSettings(); - const token = this.issueToken(userForToken, appSettings); + const token = this.issueToken(safeClaims, appSettings); @@ - return { user: newUser, token }; + sanitizedUser.avatarImage = newUser.avatarImage; + return { user: sanitizedUser, token };Also applies to: 81-82
♻️ Duplicate comments (2)
server/src/service/business/userService.js (2)
100-111: JWT claim allowlisting; sanitize once (don’t spread user._doc).Avoid packing excess fields/PII into JWT and response; build minimal claims and a sanitized response explicitly.
- // Remove password from user object. Should this be abstracted to DB layer? - const userWithoutPassword = { ...user._doc }; - delete userWithoutPassword.password; - delete userWithoutPassword.avatarImage; + // Build minimal safe claims and response + const { _id, email, role, teamId, firstName, lastName } = user._doc || {}; + const safeClaims = { _id, email, role, teamId }; + const sanitizedUser = { _id, email, role, teamId, firstName, lastName }; // Happy path, return token const appSettings = await this.settingsService.getSettings(); - const token = this.issueToken(userWithoutPassword, appSettings); - // reset avatar image - userWithoutPassword.avatarImage = user.avatarImage; - return { user: userWithoutPassword, token }; + const token = this.issueToken(safeClaims, appSettings); + // Attach avatar only to response, not JWT claims + sanitizedUser.avatarImage = user.avatarImage; + return { user: sanitizedUser, token };
112-124: Don’t mask server faults as 401; log auth as warn, bubble 5xx.Only return 401 for credential failures; let non-auth errors surface.
- } catch (error) { - // Log specific errors while returning generic message for security - this.logger.error({ - message: "Login attempt failed", - service: SERVICE_NAME, - method: "loginUser", - error: error.message, - stack: error.stack, - }); - - // If user is not found, throw a generic authentication error for security - // Don't reveal whether user exists or password is wrong - throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword); - } + } catch (error) { + const isAuth = + error?.status === 401 || + error?.name === "AuthenticationError" || + this.errorService?.isAuthenticationError?.(error) === true; + + this.logger[isAuth ? "warn" : "error"]({ + message: "Login attempt failed", + service: SERVICE_NAME, + method: "loginUser", + error: error.message, + stack: error.stack, + }); + + if (isAuth) throw error; // preserve 401 thrown above + // Bubble non-auth errors so they surface as 5xx + throw error; + }
🧹 Nitpick comments (1)
server/src/service/business/userService.js (1)
96-98: Nit: simplify boolean check.Prefer truthiness to avoid strict-boolean coupling.
- if (match !== true) { + if (!match) {
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
server/src/service/business/userService.js(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
server/src/service/business/userService.js (1)
server/src/db/mongo/modules/userModule.js (1)
SERVICE_NAME(1-1)
🔇 Additional comments (2)
server/src/service/business/userService.js (2)
90-93: Good: generic auth error blocks user enumeration.Null/method checks with a single generic auth error keep responses uniform.
136-139: LGTM: consistent 403 for edit-password precheck.Using 403 with a generic message avoids info leaks and interceptor side effects.
|
@karenvicent fixed conflicts. |
Summary
Fixes critical login flow issue where backend was returning raw localization keys (
authIncorrectPassword) instead of proper user-facing error messages. This issue made error handling brittle and caused poor user experience during authentication failures.Problem Description
Changes Made
Backend Fixes
Fixed translation service path (
translationService.js):locales/tosrc/locales/directoryImproved login security (
userService.js):Frontend Fixes
Login/index.jsx):=== "Incorrect password")Testing Results
✅ Before Fix: Backend returned
"authIncorrectPassword"(raw key)✅ After Fix: Backend returns
"Incorrect password"(translated message)Impact
Fixes #2768
Summary by CodeRabbit