Skip to content

Commit 0bd8f91

Browse files
committed
Security Enhancements, Depndency Upgrades, Enahncements
Security Enhancements - Added URL validation for redirects through session.returnTo (CWE-601). - Fixed OAuth state parameter generation and handling to address CSRF attack vectors in the OAuth workflow. - Added additional sanitization for user input in database queries using $eq in MongoDB. API and Integration: - Unified formatting for authentication parameters in route definitions and passport.js configuration. - Refactored common code for OAuth 2 token processing in passport strategies to improve maintainability. - Reworked the GitHub and Twitch API integration examples with additional data from the APIs. - Reworked the Twilio API integration example to use Twilio’s sandbox servers and test phone numbers. - Upgraded the Pinterest API example to use v5 calls instead of the broken v1. - Reworked the Tumblr API integration example with additional data from the API. - Added a properly working OAuth 1.0a integration for Tumblr. - Removed sign-in by Snapchat due to increased difficulty for developers and a focus on hackathon participants. - Removed Foursquare OAuth authorization and updated the API demo with new examples. Update/Upgrades: - Migrated from the unmaintained passport-linkedin-oauth2 to a passport-openidconnect strategy. o Added support and examples for openid-client. - Migrated from the deprecated paypal-rest-sdk to an example without the SDK, providing OAuth calls depending on the page state. - Migrated from the unmaintained bootstrap-social to a fork that can be easily patched and updated. - Migrated eslint to v9, and its new config format (breaking change). - Migrated Husky to v9, and its new config format (breaking change). Fixed Windows commit issue. - Updated dependencies. - Added temporary patch files for connect-flash and passport-openidconnect based on pending pull requests or issues on GitHub. Other: - Fixed a bug that prevented profile pictures from being displayed. - Added authentication link/unlink options to the user profile page for all OAuth/Identity providers. - Fixed typos, broken links, and minor formatting alignment issues on various pages. - Fixed spelling errors in startup information displayed in the console. - Refactored URL validation in unit tests for Gravatar generation to conform with CodeQL rules. Even though CodeQL does vulnerability checks, this is not a security issue since it is unit tests. - Updated the placeholder main.js to use the current format (not deprecated JS). - Updated the GitHub repo worker/runner configs to use proper permissions - Return exit code 1 if there is a database connection issue at startup. - Added the --trace-deprecation flag to startup to provide better information on runtime deprecation warnings. - .gitignore file to exclude the uploads path. - Updated the copyright year. - Updated documentation.
1 parent ed7f82d commit 0bd8f91

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+5631
-3605
lines changed

.env.example

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ NYT_KEY=9548be6f3a64163d23e1539f067fcabd:5:68537648
2525
LASTFM_KEY=c8c0ea1c4a6b199b3429722512fbd17f
2626
LASTFM_SECRET=is cb7857b8fba83f819ea46ca13681fe71
2727

28-
SNAPCHAT_ID=181f414f-9581-4498-be9a-a223d024cf10
29-
SNAPCHAT_SECRET=DyswCZGyuZl5BBEA1yWlcjyAoONB-_qw8WNodhc4hr4
30-
3128
FACEBOOK_ID=754220301289665
3229
FACEBOOK_SECRET=41860e58c256a3d7ad8267d3c1939a4a
3330

.eslintrc

Lines changed: 0 additions & 23 deletions
This file was deleted.

.github/workflows/node.js.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2-
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
32

43
name: Node.js CI
54

@@ -9,20 +8,24 @@ on:
98
pull_request:
109
branches: [ "master" ]
1110

11+
permissions:
12+
contents: read
13+
pull-requests: write
14+
1215
jobs:
1316
build:
1417

1518
runs-on: ubuntu-latest
1619

1720
strategy:
1821
matrix:
19-
node-version: [18.x, 20.x]
22+
node-version: [ 22.x]
2023
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
2124

2225
steps:
23-
- uses: actions/checkout@v3
26+
- uses: actions/checkout@v4
2427
- name: Use Node.js ${{ matrix.node-version }}
25-
uses: actions/setup-node@v3
28+
uses: actions/setup-node@v4
2629
with:
2730
node-version: ${{ matrix.node-version }}
2831
cache: 'npm'

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public/css/main.css
2727
node_modules
2828
bower_components
2929

30+
# Uploads
31+
uploads
32+
3033
# Editors
3134
.idea
3235
.vscode

.husky/pre-commit

100644100755
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
npm run lintStage
1+
#!/usr/bin/env sh
2+
. "$(dirname -- "$0")/_/husky.sh"
3+
4+
npm test

CHANGELOG.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,48 @@
11
# Changelog
22
---------
33

4+
### 8.1.0 (February 1, 2025)
5+
Security Enhancements
6+
- Added URL validation for redirects through session.returnTo (CWE-601).
7+
- Fixed OAuth state parameter generation and handling to address CSRF attack vectors in the OAuth workflow.
8+
- Added additional sanitization for user input in database queries using $eq in MongoDB.
9+
10+
API and Integration:
11+
- Unified formatting for authentication parameters in route definitions and passport.js configuration.
12+
- Refactored common code for OAuth 2 token processing in passport strategies to improve maintainability.
13+
- Reworked the GitHub and Twitch API integration examples with additional data from the APIs.
14+
- Reworked the Twilio API integration example to use Twilio’s sandbox servers and test phone numbers.
15+
- Upgraded the Pinterest API example to use v5 calls instead of the broken v1.
16+
- Reworked the Tumblr API integration example with additional data from the API.
17+
- Added a properly working OAuth 1.0a integration for Tumblr.
18+
- Removed sign-in by Snapchat due to increased difficulty for developers and a focus on hackathon participants.
19+
- Removed Foursquare OAuth authorization and updated the API demo with new examples.
20+
21+
Update/Upgrades:
22+
- Dropped support for Nodejs < 22 due to ESM module import issues prior to that version.
23+
- Migrated from the unmaintained passport-linkedin-oauth2 to a passport-openidconnect strategy.
24+
o Added support and examples for openid-client.
25+
- Migrated from the deprecated paypal-rest-sdk to an example without the SDK, providing OAuth calls depending on the page state.
26+
- Migrated from the unmaintained bootstrap-social to a fork that can be easily patched and updated.
27+
- Migrated eslint to v9, and its new config format (breaking change).
28+
- Migrated Husky to v9, and its new config format (breaking change). Fixed Windows commit issue.
29+
- Updated dependencies.
30+
- Added temporary patch files for connect-flash and passport-openidconnect based on pending pull requests or issues on GitHub.
31+
32+
Other:
33+
- Fixed a bug that prevented profile pictures from being displayed.
34+
- Added authentication link/unlink options to the user profile page for all OAuth/Identity providers.
35+
- Fixed typos, broken links, and minor formatting alignment issues on various pages.
36+
- Fixed spelling errors in startup information displayed in the console.
37+
- Refactored URL validation in unit tests for Gravatar generation to conform with CodeQL rules. Even though CodeQL does vulnerability checks, this is not a security issue since it is unit tests.
38+
- Updated the placeholder main.js to use the current format (not deprecated JS).
39+
- Updated the GitHub repo worker/runner configs to use proper permissions
40+
- Return exit code 1 if there is a database connection issue at startup.
41+
- Added the --trace-deprecation flag to startup to provide better information on runtime deprecation warnings.
42+
- .gitignore file to exclude the uploads path.
43+
- Updated the copyright year.
44+
- Updated documentation.
45+
446
### 8.0.0 (July 28, 2023)
547

648
- Security: Renamed the cookie and set secure attribute for cookie transmission when https is present
@@ -47,7 +89,6 @@ API example changes:
4789
- Added missing parameters for the Lob's new API requirements
4890
- Improved the Last.fm API example as the artist image is no longer vended by last.fm
4991

50-
5192
### 7.0.0 (Mar 26, 2022)
5293
- Dropped support for Node.js <16
5394
- Switched to Bootstrap 5

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2014-2023 Sahat Yalkabov
3+
Copyright (c) 2014-2025 Sahat Yalkabov
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Features
7070
--------
7171
- Login
7272
- **Local Authentication** using Email and Password
73-
- **OAuth 2.0 Authentication:** Sign in with Google, Facebook, X (Twitter), LinkedIn, Twitch, Github, Snapchat
73+
- **OAuth 2.0 Authentication:** Sign in with Google, Facebook, X (Twitter), LinkedIn, Twitch, Github
7474
- **User Profile and Account Management**
7575
- Gravatar
7676
- Profile Details
@@ -148,7 +148,7 @@ After completing step 1 and locally installing MongoDB, you should be able to ac
148148
- reCAPTCHA
149149
- For contact form submission
150150
- OAuth for social logins (Sign in with / Login with)
151-
- Depending on your application need, obtain keys from Google, Facebook, X (Twitter), LinkedIn, Twitch, GitHub, Snapchat. You don't have to obtain valid keys for any provider that you don't need. Just remove the buttons and links in the login and account pug views before your demo.
151+
- Depending on your application need, obtain keys from Google, Facebook, X (Twitter), LinkedIn, Twitch, GitHub. You don't have to obtain valid keys for any provider that you don't need. Just remove the buttons and links in the login and account pug views before your demo.
152152
- API keys for service providers in the API Examples if you are planning to use them.
153153

154154
- MongoDB Atlas
@@ -220,23 +220,6 @@ Obtain SMTP credentials from a provider for transactional emails. Set the SMTP_
220220
- Click on **Create Client ID** button
221221
- Copy and paste *Client ID* and *Client secret* keys into `.env`
222222

223-
<hr>
224-
225-
<img src="https://seeklogo.com/images/S/snapchat-logo-F20CDB1199-seeklogo.com.png" height="90">
226-
227-
- Visit <a href="https://kit.snapchat.com/portal" target="_blank">Snap Kit Developer Portal</a>
228-
- Click on the **+** button to create an app
229-
- Enter a name for your app
230-
- Enable the scopes that you will want to use in your app
231-
- Click on the **Continue** button
232-
- Find the **Kits** section and make sure that **Login Kit** is enabled
233-
- Find the **Redirect URLs** section, click the **+ Add** button, and enter your BASE_URL value followed by /auth/snapchat/callback (i.e. `http://localhost:8080/auth/snapchat/callback` )
234-
- Find the **Development Environment** section. Click the **Generate** button next to the *Confidential OAuth2 Client* heading within it.
235-
- Copy and paste the generated *Private Key* and *OAuth2 Client ID* keys into `.env`
236-
- **Note:** *OAuth2 Client ID* is **SNAPCHAT_ID**, *Private Key* is **SNAPCHAT_SECRET** in `.env`
237-
- To prepare the app for submission, fill out the rest of the required fields: *Category*, *Description*, *Privacy Policy Url*, and *App Icon*
238-
239-
240223
<hr>
241224

242225
<img src="https://en.facebookbrand.com/wp-content/uploads/2019/04/f_logo_RGB-Hex-Blue_512.png" width="90">
@@ -490,7 +473,6 @@ List of Packages
490473
| passport-local | Sign-in with Username and Password plugin. |
491474
| passport-oauth | Allows you to set up your own OAuth 1.0a and OAuth 2.0 strategies. |
492475
| passport-oauth2-refresh | A library to refresh OAuth 2.0 access tokens using refresh tokens. |
493-
| passport-snapchat | Sign-in with Snapchat plugin. |
494476
| passport-steam-openid | OpenID 2.0 Steam plugin. |
495477
| patch-package | Fix broken node modules ahead of fixes by maintainers. |
496478
| paypal-rest-sdk | PayPal APIs library. |
@@ -573,8 +555,8 @@ That's a custom error message defined in `app.js` to indicate that there was a p
573555
```js
574556
mongoose.connection.on('error', (err) => {
575557
console.error(err);
576-
console.log('%s MongoDB connection error. Please make sure MongoDB is running.', chalk.red(''));
577-
process.exit();
558+
console.log('%s MongoDB connection error. Please make sure MongoDB is running.');
559+
process.exit(1);
578560
});
579561
```
580562
You need to have a MongoDB server running before launching `app.js`. You can download MongoDB [here](https://www.mongodb.com/download-center/community), or install it via a package manager.
@@ -1431,7 +1413,7 @@ License
14311413
14321414
The MIT License (MIT)
14331415
1434-
Copyright (c) 2014-2023 Sahat Yalkabov
1416+
Copyright (c) 2014-2025 Sahat Yalkabov
14351417
14361418
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14371419

app.js

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ mongoose.connect(process.env.MONGODB_URI);
6969
mongoose.connection.on('error', (err) => {
7070
console.error(err);
7171
console.log('%s MongoDB connection error. Please make sure MongoDB is running.');
72-
process.exit();
72+
process.exit(1);
7373
});
7474

7575
/**
@@ -115,16 +115,29 @@ app.use((req, res, next) => {
115115
next();
116116
});
117117
app.use((req, res, next) => {
118+
// Function to validate if the URL is a safe relative path
119+
const isSafeRedirect = (url) => /^\/[a-zA-Z0-9/]*$/.test(url);
120+
118121
// After successful login, redirect back to the intended page
119122
if (!req.user
120123
&& req.path !== '/login'
121124
&& req.path !== '/signup'
122125
&& !req.path.match(/^\/auth/)
123126
&& !req.path.match(/\./)) {
124-
req.session.returnTo = req.originalUrl;
127+
const returnTo = req.originalUrl;
128+
if (isSafeRedirect(returnTo)) {
129+
req.session.returnTo = returnTo;
130+
} else {
131+
req.session.returnTo = '/';
132+
}
125133
} else if (req.user
126134
&& (req.path === '/account' || req.path.match(/^\/api/))) {
127-
req.session.returnTo = req.originalUrl;
135+
const returnTo = req.originalUrl;
136+
if (isSafeRedirect(returnTo)) {
137+
req.session.returnTo = returnTo;
138+
} else {
139+
req.session.returnTo = '/';
140+
}
128141
}
129142
next();
130143
});
@@ -170,10 +183,10 @@ app.post('/api/stripe', apiController.postStripe);
170183
app.get('/api/scraping', apiController.getScraping);
171184
app.get('/api/twilio', apiController.getTwilio);
172185
app.post('/api/twilio', apiController.postTwilio);
173-
app.get('/api/foursquare', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getFoursquare);
186+
app.get('/api/foursquare', apiController.getFoursquare);
174187
app.get('/api/tumblr', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getTumblr);
175188
app.get('/api/facebook', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getFacebook);
176-
app.get('/api/github', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getGithub);
189+
app.get('/api/github', apiController.getGithub);
177190
app.get('/api/twitch', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getTwitch);
178191
app.get('/api/paypal', apiController.getPayPal);
179192
app.get('/api/paypal/success', apiController.getPayPalSuccess);
@@ -193,27 +206,23 @@ app.get('/api/quickbooks', passportConfig.isAuthenticated, passportConfig.isAuth
193206
/**
194207
* OAuth authentication routes. (Sign in)
195208
*/
196-
app.get('/auth/snapchat', passport.authenticate('snapchat'));
197-
app.get('/auth/snapchat/callback', passport.authenticate('snapchat', { failureRedirect: '/login' }), (req, res) => {
198-
res.redirect(req.session.returnTo || '/');
199-
});
200-
app.get('/auth/facebook', passport.authenticate('facebook', { scope: ['email', 'public_profile'] }));
209+
app.get('/auth/facebook', passport.authenticate('facebook'));
201210
app.get('/auth/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login' }), (req, res) => {
202211
res.redirect(req.session.returnTo || '/');
203212
});
204213
app.get('/auth/github', passport.authenticate('github'));
205214
app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => {
206215
res.redirect(req.session.returnTo || '/');
207216
});
208-
app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email', 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets.readonly'], accessType: 'offline', prompt: 'consent' }));
217+
app.get('/auth/google', passport.authenticate('google'));
209218
app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => {
210219
res.redirect(req.session.returnTo || '/');
211220
});
212221
app.get('/auth/twitter', passport.authenticate('twitter'));
213222
app.get('/auth/twitter/callback', passport.authenticate('twitter', { failureRedirect: '/login' }), (req, res) => {
214223
res.redirect(req.session.returnTo || '/');
215224
});
216-
app.get('/auth/linkedin', passport.authenticate('linkedin', { state: 'SOME STATE' }));
225+
app.get('/auth/linkedin', passport.authenticate('linkedin'));
217226
app.get('/auth/linkedin/callback', passport.authenticate('linkedin', { failureRedirect: '/login' }), (req, res) => {
218227
res.redirect(req.session.returnTo || '/');
219228
});
@@ -225,23 +234,19 @@ app.get('/auth/twitch/callback', passport.authenticate('twitch', { failureRedire
225234
/**
226235
* OAuth authorization routes. (API examples)
227236
*/
228-
app.get('/auth/foursquare', passport.authorize('foursquare'));
229-
app.get('/auth/foursquare/callback', passport.authorize('foursquare', { failureRedirect: '/api' }), (req, res) => {
230-
res.redirect('/api/foursquare');
231-
});
232237
app.get('/auth/tumblr', passport.authorize('tumblr'));
233238
app.get('/auth/tumblr/callback', passport.authorize('tumblr', { failureRedirect: '/api' }), (req, res) => {
234-
res.redirect('/api/tumblr');
239+
res.redirect(req.session.returnTo);
235240
});
236-
app.get('/auth/steam', passport.authorize('steam-openid', { state: 'SOME STATE' }));
241+
app.get('/auth/steam', passport.authorize('steam-openid'));
237242
app.get('/auth/steam/callback', passport.authorize('steam-openid', { failureRedirect: '/api' }), (req, res) => {
238243
res.redirect(req.session.returnTo);
239244
});
240-
app.get('/auth/pinterest', passport.authorize('pinterest', { scope: 'read_public write_public' }));
245+
app.get('/auth/pinterest', passport.authorize('pinterest'));
241246
app.get('/auth/pinterest/callback', passport.authorize('pinterest', { failureRedirect: '/login' }), (req, res) => {
242-
res.redirect('/api/pinterest');
247+
res.redirect(req.session.returnTo);
243248
});
244-
app.get('/auth/quickbooks', passport.authorize('quickbooks', { scope: ['com.intuit.quickbooks.accounting'], state: 'SOME STATE' }));
249+
app.get('/auth/quickbooks', passport.authorize('quickbooks'));
245250
app.get('/auth/quickbooks/callback', passport.authorize('quickbooks', { failureRedirect: '/login' }), (req, res) => {
246251
res.redirect(req.session.returnTo);
247252
});
@@ -274,7 +279,7 @@ app.listen(app.get('port'), () => {
274279
const port = parseInt(BASE_URL.slice(colonIndex + 1), 10);
275280

276281
if (!BASE_URL.startsWith('http://localhost')) {
277-
console.log(`The BASE_URL env variable is set to ${BASE_URL}. If you directly test the application through http://localhost:${app.get('port')} instead of the BASE_URL, it may cause a CSRF mismatch or an Oauth authentication failur. To avoid the issues, change the BASE_URL or configure your proxy to match it.\n`);
282+
console.log(`The BASE_URL env variable is set to ${BASE_URL}. If you directly test the application through http://localhost:${app.get('port')} instead of the BASE_URL, it may cause a CSRF mismatch or an Oauth authentication failure. To avoid the issues, change the BASE_URL or configure your proxy to match it.\n`);
278283
} else if (app.get('port') !== port) {
279284
console.warn(`WARNING: The BASE_URL environment variable and the App have a port mismatch. If you plan to view the app in your browser using the localhost address, you may need to adjust one of the ports to make them match. BASE_URL: ${BASE_URL}\n`);
280285
}

0 commit comments

Comments
 (0)