-
-
Notifications
You must be signed in to change notification settings - Fork 10
fix(auth): log OAuth callback errors and drop useless error={} redirect #3506
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -441,11 +441,19 @@ const oauthCallback = async (req, res, next) => { | |||||
| passport.authenticate(strategy, (err, user) => { | ||||||
| const url = getBaseUrl(); | ||||||
| if (err) { | ||||||
| const _err = JSON.stringify(err); | ||||||
| logger.error( | ||||||
| { err: { message: err?.message, code: err?.code, stack: err?.stack }, strategy }, | ||||||
| 'OAuth callback failed', | ||||||
| ); | ||||||
|
Comment on lines
+444
to
+447
|
||||||
| const _err = encodeURIComponent(err?.message || err?.code || 'oauth_error'); | ||||||
| const path = 'token?message=Unprocessable%20Entity'; | ||||||
| res.redirect(302, `${url}/${path}&error=${_err}`); | ||||||
| } else if (!user) { | ||||||
| const _err = JSON.stringify(err); | ||||||
| logger.error( | ||||||
| { err: { message: err?.message, code: err?.code, stack: err?.stack }, strategy }, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚪ LOW RISK In this branch, 'err' is guaranteed to be falsy because it follows an 'if (err)' check. Logging 'err?.message', 'err?.code', or 'err?.stack' will result in 'undefined' values in your logs, providing no context. Suggested fix:
Suggested change
|
||||||
| 'OAuth callback failed', | ||||||
| ); | ||||||
|
Comment on lines
+452
to
+455
|
||||||
| const _err = encodeURIComponent(err?.message || err?.code || 'oauth_no_user'); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚪ LOW RISK Suggestion: Since
Comment on lines
441
to
+456
|
||||||
| const path = 'token?message=Could%20not%20define%20user%20in%20oAuth'; | ||||||
| res.redirect(302, `${url}/${path}&error=${_err}`); | ||||||
| } else { | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ import passport from 'passport'; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { bootstrap } from '../../../lib/app.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import mongooseService from '../../../lib/services/mongoose.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import config from '../../../config/index.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import logger from '../../../lib/services/logger.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Unit tests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -645,6 +646,78 @@ describe('Auth integration tests:', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| authenticateSpy.mockRestore(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test('should log and redirect with message when classic web oAuth errors out', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const oauthErr = new Error('token exchange failed'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| oauthErr.code = 'OAUTH_TOKEN_EXCHANGE'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const authenticateSpy = jest.spyOn(passport, 'authenticate').mockImplementationOnce( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (strategy, callback) => () => callback(oauthErr, null), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const loggerSpy = jest.spyOn(logger, 'error').mockImplementation(() => {}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const redirectCalls = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mockReq = { params: { strategy: 'google' }, body: {} }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mockRes = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cookie() { return this; }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| redirect(code, url) { redirectCalls.push({ code, url }); }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await AuthController.oauthCallback(mockReq, mockRes, () => {}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(loggerSpy).toHaveBeenCalledWith( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect.objectContaining({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err: expect.objectContaining({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: 'token exchange failed', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| code: 'OAUTH_TOKEN_EXCHANGE', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stack: expect.any(String), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strategy: 'google', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'OAuth callback failed', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+665
to
+675
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(redirectCalls[0].code).toBe(302); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Redirect must carry the actual error message, not an empty object | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(redirectCalls[0].url).toContain('error='); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(redirectCalls[0].url).not.toContain('error={}'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(redirectCalls[0].url).toContain(encodeURIComponent('token exchange failed')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loggerSpy.mockRestore(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| authenticateSpy.mockRestore(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+655
to
+684
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test('should log and redirect with sensible message when no user is returned by passport', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const authenticateSpy = jest.spyOn(passport, 'authenticate').mockImplementationOnce( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (strategy, callback) => () => callback(null, null), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const loggerSpy = jest.spyOn(logger, 'error').mockImplementation(() => {}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const redirectCalls = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mockReq = { params: { strategy: 'google' }, body: {} }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mockRes = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cookie() { return this; }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| redirect(code, url) { redirectCalls.push({ code, url }); }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await AuthController.oauthCallback(mockReq, mockRes, () => {}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(loggerSpy).toHaveBeenCalledWith( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect.objectContaining({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err: expect.objectContaining({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| code: undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stack: undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strategy: 'google', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'OAuth callback failed', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+700
to
+710
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(redirectCalls[0].code).toBe(302); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(redirectCalls[0].url).toContain('error='); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(redirectCalls[0].url).not.toContain('error={}'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(redirectCalls[0].url).toContain('oauth_no_user'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(redirectCalls[0].url).toContain('Could%20not%20define%20user%20in%20oAuth'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loggerSpy.mockRestore(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| authenticateSpy.mockRestore(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+698
to
+718
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await AuthController.oauthCallback(mockReq, mockRes, () => {}); | |
| expect(loggerSpy).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| err: expect.objectContaining({ | |
| message: undefined, | |
| code: undefined, | |
| stack: undefined, | |
| }), | |
| strategy: 'google', | |
| }), | |
| 'OAuth callback failed', | |
| ); | |
| expect(redirectCalls[0].code).toBe(302); | |
| expect(redirectCalls[0].url).toContain('error='); | |
| expect(redirectCalls[0].url).not.toContain('error={}'); | |
| expect(redirectCalls[0].url).toContain('oauth_no_user'); | |
| expect(redirectCalls[0].url).toContain('Could%20not%20define%20user%20in%20oAuth'); | |
| loggerSpy.mockRestore(); | |
| authenticateSpy.mockRestore(); | |
| try { | |
| await AuthController.oauthCallback(mockReq, mockRes, () => {}); | |
| expect(loggerSpy).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| err: expect.objectContaining({ | |
| message: undefined, | |
| code: undefined, | |
| stack: undefined, | |
| }), | |
| strategy: 'google', | |
| }), | |
| 'OAuth callback failed', | |
| ); | |
| expect(redirectCalls[0].code).toBe(302); | |
| expect(redirectCalls[0].url).toContain('error='); | |
| expect(redirectCalls[0].url).not.toContain('error={}'); | |
| expect(redirectCalls[0].url).toContain('oauth_no_user'); | |
| expect(redirectCalls[0].url).toContain('Could%20not%20define%20user%20in%20oAuth'); | |
| } finally { | |
| loggerSpy.mockRestore(); | |
| authenticateSpy.mockRestore(); | |
| } |
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.
🟡 MEDIUM RISK
Suggestion: The logic for logging and redirecting on failure is duplicated across the 'err' and '!user' branches. Consolidating this logic into a single path or helper function would make the controller more maintainable.