Skip to content

Commit

Permalink
Security enhancements, dependency upgrades, bug fixes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
YasharF committed Feb 1, 2025
1 parent ed7f82d commit 93d256f
Show file tree
Hide file tree
Showing 43 changed files with 5,663 additions and 3,618 deletions.
3 changes: 0 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ NYT_KEY=9548be6f3a64163d23e1539f067fcabd:5:68537648
LASTFM_KEY=c8c0ea1c4a6b199b3429722512fbd17f
LASTFM_SECRET=is cb7857b8fba83f819ea46ca13681fe71

SNAPCHAT_ID=181f414f-9581-4498-be9a-a223d024cf10
SNAPCHAT_SECRET=DyswCZGyuZl5BBEA1yWlcjyAoONB-_qw8WNodhc4hr4

FACEBOOK_ID=754220301289665
FACEBOOK_SECRET=41860e58c256a3d7ad8267d3c1939a4a

Expand Down
23 changes: 0 additions & 23 deletions .eslintrc

This file was deleted.

56 changes: 39 additions & 17 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
# 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
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Node.js CI
name: Node.js CI with CodeQL

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

permissions:
contents: read
pull-requests: write
security-events: write

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 20.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
node-version: [ 22.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm install
- run: npm run lint
- run: npm run test

codeql:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
matrix:
language: [ 'javascript', 'css', 'html', 'pug' ]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm install
- run: npm run lint
- run: npm run test
- name: Checkout repository
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}

- name: Autobuild
uses: github/codeql-action/autobuild@v2

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public/css/main.css
node_modules
bower_components

# Uploads
uploads

# Editors
.idea
.vscode
Expand Down
5 changes: 4 additions & 1 deletion .husky/pre-commit
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
npm run lintStage
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm test
43 changes: 42 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,48 @@
# Changelog
---------

### 8.1.0 (February 1, 2025)
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:
- Dropped support for Nodejs < 22 due to ESM module import issues prior to that version.
- Migrated from the unmaintained passport-linkedin-oauth2 to a passport-openidconnect strategy.
--- 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.

### 8.0.0 (July 28, 2023)

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


### 7.0.0 (Mar 26, 2022)
- Dropped support for Node.js <16
- Switched to Bootstrap 5
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2014-2023 Sahat Yalkabov
Copyright (c) 2014-2025 Sahat Yalkabov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
28 changes: 5 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Features
--------
- Login
- **Local Authentication** using Email and Password
- **OAuth 2.0 Authentication:** Sign in with Google, Facebook, X (Twitter), LinkedIn, Twitch, Github, Snapchat
- **OAuth 2.0 Authentication:** Sign in with Google, Facebook, X (Twitter), LinkedIn, Twitch, Github
- **User Profile and Account Management**
- Gravatar
- Profile Details
Expand Down Expand Up @@ -148,7 +148,7 @@ After completing step 1 and locally installing MongoDB, you should be able to ac
- reCAPTCHA
- For contact form submission
- OAuth for social logins (Sign in with / Login with)
- 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.
- 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.
- API keys for service providers in the API Examples if you are planning to use them.

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

<hr>

<img src="https://seeklogo.com/images/S/snapchat-logo-F20CDB1199-seeklogo.com.png" height="90">

- Visit <a href="https://kit.snapchat.com/portal" target="_blank">Snap Kit Developer Portal</a>
- Click on the **+** button to create an app
- Enter a name for your app
- Enable the scopes that you will want to use in your app
- Click on the **Continue** button
- Find the **Kits** section and make sure that **Login Kit** is enabled
- 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` )
- Find the **Development Environment** section. Click the **Generate** button next to the *Confidential OAuth2 Client* heading within it.
- Copy and paste the generated *Private Key* and *OAuth2 Client ID* keys into `.env`
- **Note:** *OAuth2 Client ID* is **SNAPCHAT_ID**, *Private Key* is **SNAPCHAT_SECRET** in `.env`
- To prepare the app for submission, fill out the rest of the required fields: *Category*, *Description*, *Privacy Policy Url*, and *App Icon*


<hr>

<img src="https://en.facebookbrand.com/wp-content/uploads/2019/04/f_logo_RGB-Hex-Blue_512.png" width="90">
Expand Down Expand Up @@ -490,7 +473,6 @@ List of Packages
| passport-local | Sign-in with Username and Password plugin. |
| passport-oauth | Allows you to set up your own OAuth 1.0a and OAuth 2.0 strategies. |
| passport-oauth2-refresh | A library to refresh OAuth 2.0 access tokens using refresh tokens. |
| passport-snapchat | Sign-in with Snapchat plugin. |
| passport-steam-openid | OpenID 2.0 Steam plugin. |
| patch-package | Fix broken node modules ahead of fixes by maintainers. |
| paypal-rest-sdk | PayPal APIs library. |
Expand Down Expand Up @@ -573,8 +555,8 @@ That's a custom error message defined in `app.js` to indicate that there was a p
```js
mongoose.connection.on('error', (err) => {
console.error(err);
console.log('%s MongoDB connection error. Please make sure MongoDB is running.', chalk.red(''));
process.exit();
console.log('%s MongoDB connection error. Please make sure MongoDB is running.');
process.exit(1);
});
```
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.
Expand Down Expand Up @@ -1431,7 +1413,7 @@ License
The MIT License (MIT)
Copyright (c) 2014-2023 Sahat Yalkabov
Copyright (c) 2014-2025 Sahat Yalkabov
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:
Expand Down
49 changes: 27 additions & 22 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ mongoose.connect(process.env.MONGODB_URI);
mongoose.connection.on('error', (err) => {
console.error(err);
console.log('%s MongoDB connection error. Please make sure MongoDB is running.');
process.exit();
process.exit(1);
});

/**
Expand Down Expand Up @@ -115,16 +115,29 @@ app.use((req, res, next) => {
next();
});
app.use((req, res, next) => {
// Function to validate if the URL is a safe relative path
const isSafeRedirect = (url) => /^\/[a-zA-Z0-9/]*$/.test(url);

// After successful login, redirect back to the intended page
if (!req.user
&& req.path !== '/login'
&& req.path !== '/signup'
&& !req.path.match(/^\/auth/)
&& !req.path.match(/\./)) {
req.session.returnTo = req.originalUrl;
const returnTo = req.originalUrl;
if (isSafeRedirect(returnTo)) {
req.session.returnTo = returnTo;
} else {
req.session.returnTo = '/';
}
} else if (req.user
&& (req.path === '/account' || req.path.match(/^\/api/))) {
req.session.returnTo = req.originalUrl;
const returnTo = req.originalUrl;
if (isSafeRedirect(returnTo)) {
req.session.returnTo = returnTo;
} else {
req.session.returnTo = '/';
}
}
next();
});
Expand Down Expand Up @@ -170,10 +183,10 @@ app.post('/api/stripe', apiController.postStripe);
app.get('/api/scraping', apiController.getScraping);
app.get('/api/twilio', apiController.getTwilio);
app.post('/api/twilio', apiController.postTwilio);
app.get('/api/foursquare', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getFoursquare);
app.get('/api/foursquare', apiController.getFoursquare);
app.get('/api/tumblr', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getTumblr);
app.get('/api/facebook', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getFacebook);
app.get('/api/github', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getGithub);
app.get('/api/github', apiController.getGithub);
app.get('/api/twitch', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getTwitch);
app.get('/api/paypal', apiController.getPayPal);
app.get('/api/paypal/success', apiController.getPayPalSuccess);
Expand All @@ -193,27 +206,23 @@ app.get('/api/quickbooks', passportConfig.isAuthenticated, passportConfig.isAuth
/**
* OAuth authentication routes. (Sign in)
*/
app.get('/auth/snapchat', passport.authenticate('snapchat'));
app.get('/auth/snapchat/callback', passport.authenticate('snapchat', { failureRedirect: '/login' }), (req, res) => {
res.redirect(req.session.returnTo || '/');
});
app.get('/auth/facebook', passport.authenticate('facebook', { scope: ['email', 'public_profile'] }));
app.get('/auth/facebook', passport.authenticate('facebook'));
app.get('/auth/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login' }), (req, res) => {
res.redirect(req.session.returnTo || '/');
});
app.get('/auth/github', passport.authenticate('github'));
app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => {
res.redirect(req.session.returnTo || '/');
});
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' }));
app.get('/auth/google', passport.authenticate('google'));
app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => {
res.redirect(req.session.returnTo || '/');
});
app.get('/auth/twitter', passport.authenticate('twitter'));
app.get('/auth/twitter/callback', passport.authenticate('twitter', { failureRedirect: '/login' }), (req, res) => {
res.redirect(req.session.returnTo || '/');
});
app.get('/auth/linkedin', passport.authenticate('linkedin', { state: 'SOME STATE' }));
app.get('/auth/linkedin', passport.authenticate('linkedin'));
app.get('/auth/linkedin/callback', passport.authenticate('linkedin', { failureRedirect: '/login' }), (req, res) => {
res.redirect(req.session.returnTo || '/');
});
Expand All @@ -225,23 +234,19 @@ app.get('/auth/twitch/callback', passport.authenticate('twitch', { failureRedire
/**
* OAuth authorization routes. (API examples)
*/
app.get('/auth/foursquare', passport.authorize('foursquare'));
app.get('/auth/foursquare/callback', passport.authorize('foursquare', { failureRedirect: '/api' }), (req, res) => {
res.redirect('/api/foursquare');
});
app.get('/auth/tumblr', passport.authorize('tumblr'));
app.get('/auth/tumblr/callback', passport.authorize('tumblr', { failureRedirect: '/api' }), (req, res) => {
res.redirect('/api/tumblr');
res.redirect(req.session.returnTo);
});
app.get('/auth/steam', passport.authorize('steam-openid', { state: 'SOME STATE' }));
app.get('/auth/steam', passport.authorize('steam-openid'));
app.get('/auth/steam/callback', passport.authorize('steam-openid', { failureRedirect: '/api' }), (req, res) => {
res.redirect(req.session.returnTo);
});
app.get('/auth/pinterest', passport.authorize('pinterest', { scope: 'read_public write_public' }));
app.get('/auth/pinterest', passport.authorize('pinterest'));
app.get('/auth/pinterest/callback', passport.authorize('pinterest', { failureRedirect: '/login' }), (req, res) => {
res.redirect('/api/pinterest');
res.redirect(req.session.returnTo);
});
app.get('/auth/quickbooks', passport.authorize('quickbooks', { scope: ['com.intuit.quickbooks.accounting'], state: 'SOME STATE' }));
app.get('/auth/quickbooks', passport.authorize('quickbooks'));
app.get('/auth/quickbooks/callback', passport.authorize('quickbooks', { failureRedirect: '/login' }), (req, res) => {
res.redirect(req.session.returnTo);
});
Expand Down Expand Up @@ -274,7 +279,7 @@ app.listen(app.get('port'), () => {
const port = parseInt(BASE_URL.slice(colonIndex + 1), 10);

if (!BASE_URL.startsWith('http://localhost')) {
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`);
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`);
} else if (app.get('port') !== port) {
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`);
}
Expand Down
Loading

0 comments on commit 93d256f

Please sign in to comment.