Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 3 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"env": {
"browser": true,
"es2021": true,
"node": true
"node": true,
"jest": true
},
"extends": "eslint:recommended",
"parserOptions": {
Expand All @@ -16,7 +17,7 @@
],
"linebreak-style": [
"error",
"windows"
"unix"
],
"quotes": [
"error",
Expand Down
9 changes: 3 additions & 6 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,16 @@ jobs:

strategy:
matrix:
node-version: [ "latest" ]
node-version: [18.x, 20.x, 22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm test



18 changes: 14 additions & 4 deletions .github/workflows/npm-audit.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: NPM Audit Workflowhttps://github.com/temichelle13/StudyPlanner/tree/master/.github/workflows
name: NPM Audit Workflow

on:
push:
Expand All @@ -14,10 +14,20 @@ jobs:

steps:
- name: Checkout code for audit
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm install
run: npm ci

- name: Run NPM Audit
run: npm audit --audit-level=high
run: |
npm audit --audit-level=high || {
echo "High-level vulnerabilities found. Please run 'npm audit fix' to fix them."
exit 1
}
41 changes: 21 additions & 20 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
require("dotenv").config();
const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const morgan = require("morgan");
const rateLimit = require("express-rate-limit");
const http = require("http");
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
const http = require('http');

const app = express();
const port = process.env.PORT || 3000;
Expand All @@ -13,28 +13,29 @@ app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(helmet()); // Security Headers
app.use(morgan("short"));
app.use(cors()); // Enable CORS
app.use(morgan('short'));
// Rate Limit for brute force attacks
app.use(
rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 100,
}),
rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 100,
}),
);
const server = http.createServer(app);

process.on("uncaughtException", (error) => {
console.error("Uncaught exception:", error);
server.close(() => process.exit(1));
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
server.close(() => process.exit(1));
});

server.listen(port, () => {
console.log("Server is started on port " + port);
console.log('Server is started on port ' + port);
});
process.on("unhandledRejection", (error) => {
console.error("Unhandled Rejection:", error);
process.on('unhandledRejection', (error) => {
console.error('Unhandled Rejection:', error);
});
process.on("rejectionHandled", (error) => {
console.error("Rejection handled:", error);
process.on('rejectionHandled', (error) => {
console.error('Rejection handled:', error);
});
module.exports = app;
70 changes: 35 additions & 35 deletions db.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB Connected: ' + conn.connection.host);
} catch (error) {
console.error('MongoDB connection error: ' + error.message);
process.exit(1); // Exit process with failure
}
};
mongoose.connection.on('connected', () => {
console.log('Mongoose connected to db');
});
mongoose.connection.on('error', err => {
console.error(err.message);
});
mongoose.connection.on('disconnected', () => {
console.log('Mongoose connection is disconnected');
});
process.on('SIGINT', async () => {
await mongoose.connection.close();
process.exit(0);
});
module.exports = connectDB;

const mongoose = require('mongoose');

const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB Connected: ' + conn.connection.host);
} catch (error) {
console.error('MongoDB connection error: ' + error.message);
process.exit(1); // Exit process with failure
}
};

mongoose.connection.on('connected', () => {
console.log('Mongoose connected to db');
});

mongoose.connection.on('error', err => {
console.error(err.message);
});

mongoose.connection.on('disconnected', () => {
console.log('Mongoose connection is disconnected');
});

process.on('SIGINT', async () => {
await mongoose.connection.close();
process.exit(0);
});

module.exports = connectDB;

// Add robust error handling and reconnection strategies here
140 changes: 70 additions & 70 deletions functions/resetFunc.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,74 @@

/*
This function will be run when the client SDK 'callResetPasswordFunction' and is called with an object parameter
which contains four keys: 'token', 'tokenId', 'username', and 'password', and additional parameters
for each parameter passed in as part of the argument list from the SDK.

The return object must contain a 'status' key which can be empty or one of three string values:
'success', 'pending', or 'fail'

'success': the user's password is set to the passed in 'password' parameter.

'pending': the user's password is not reset and the UserPasswordAuthProviderClient 'resetPassword' function would
need to be called with the token, tokenId, and new password via an SDK. (see below)

const Realm = require("realm");
const appConfig = {
id: "my-app-id",
timeout: 1000,
app: {
name: "my-app-name",
version: "1"
}
};
let app = new Realm.App(appConfig);
let client = app.auth.emailPassword;
await client.resetPassword(token, tokenId, newPassword);

'fail': the user's password is not reset and will not be able to log in with that password.

If an error is thrown within the function the result is the same as 'fail'.

Example below:

exports = ({ token, tokenId, username, password }, sendEmail, securityQuestionAnswer) => {
// process the reset token, tokenId, username and password
if (sendEmail) {
context.functions.execute('sendResetPasswordEmail', username, token, tokenId);
// will wait for SDK resetPassword to be called with the token and tokenId
return { status: 'pending' };
} else if (context.functions.execute('validateSecurityQuestionAnswer', username, securityQuestionAnswer)) {
// will set the users password to the password parameter
return { status: 'success' };
}

// will not reset the password
return { status: 'fail' };
};

The uncommented function below is just a placeholder and will result in failure.
*/

exports = async ({ token, tokenId, username, password }) => {
const serviceName = 'mongodb-atlas';
const dbName = 'myDatabase';
const users = context.services.get(serviceName).db(dbName).collection('users');

try {
const user = await users.findOne({ username, resetToken: token, resetTokenId: tokenId });
if (!user) {
/*
This function will be run when the client SDK 'callResetPasswordFunction' and is called with an object parameter
which contains four keys: 'token', 'tokenId', 'username', and 'password', and additional parameters
for each parameter passed in as part of the argument list from the SDK.

The return object must contain a 'status' key which can be empty or one of three string values:
'success', 'pending', or 'fail'

'success': the user's password is set to the passed in 'password' parameter.

'pending': the user's password is not reset and the UserPasswordAuthProviderClient 'resetPassword' function would
need to be called with the token, tokenId, and new password via an SDK. (see below)

const Realm = require("realm");
const appConfig = {
id: "my-app-id",
timeout: 1000,
app: {
name: "my-app-name",
version: "1"
}
};
let app = new Realm.App(appConfig);
let client = app.auth.emailPassword;
await client.resetPassword(token, tokenId, newPassword);

'fail': the user's password is not reset and will not be able to log in with that password.

If an error is thrown within the function the result is the same as 'fail'.

Example below:

exports = ({ token, tokenId, username, password }, sendEmail, securityQuestionAnswer) => {
// process the reset token, tokenId, username and password
if (sendEmail) {
context.functions.execute('sendResetPasswordEmail', username, token, tokenId);
// will wait for SDK resetPassword to be called with the token and tokenId
return { status: 'pending' };
} else if (context.functions.execute('validateSecurityQuestionAnswer', username, securityQuestionAnswer)) {
// will set the users password to the password parameter
return { status: 'success' };
}

// will not reset the password
return { status: 'fail' };
}
};

The uncommented function below is just a placeholder and will result in failure.
*/

exports = async ({ token, tokenId, username, password }) => {
const serviceName = 'mongodb-atlas';
const dbName = 'myDatabase';
const users = context.services.get(serviceName).db(dbName).collection('users'); // eslint-disable-line no-undef

try {
const user = await users.findOne({ username, resetToken: token, resetTokenId: tokenId });
if (!user) {
return { status: 'fail' };
}

const bcrypt = require('bcryptjs');
const hashed = await bcrypt.hash(password, 8);
await users.updateOne(
{ _id: user._id },
{ $set: { password: hashed }, $unset: { resetToken: '', resetTokenId: '' } }
);
return { status: 'success' };
} catch (err) {
console.error('Error in resetFunc:', err);
return { status: 'fail' };
}
const bcrypt = require('bcryptjs');
const hashed = await bcrypt.hash(password, 8);
await users.updateOne(
{ _id: user._id },
{ $set: { password: hashed }, $unset: { resetToken: '', resetTokenId: '' } }
);
return { status: 'success' };
} catch (err) {
console.error('Error in resetFunc:', err);
return { status: 'fail' };
}
};
30 changes: 15 additions & 15 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
module.exports = {
testEnvironment: 'node',
verbose: true,
collectCoverage: true,
coverageDirectory: 'coverage',
testPathIgnorePatterns: ['/node_modules/'],
coveragePathIgnorePatterns: ['/node_modules/', '/tests/'],
roots: ['<rootDir>', '<rootDir>/test'],
coverageThreshold: {
global: {
branches: 70,
functions: 80,
lines: 80,
statements: 80
}
}
testEnvironment: 'node',
verbose: true,
collectCoverage: true,
coverageDirectory: 'coverage',
testPathIgnorePatterns: ['/node_modules/'],
coveragePathIgnorePatterns: ['/node_modules/', '/tests/'],
roots: ['<rootDir>', '<rootDir>/test'],
coverageThreshold: {
global: {
branches: 70,
functions: 80,
lines: 80,
statements: 80
}
}
};
Loading
Loading