From c2463b6489a098021ad890ffef451476db836e97 Mon Sep 17 00:00:00 2001 From: Abdelouahab Bella Date: Thu, 10 Aug 2023 19:46:53 +0100 Subject: [PATCH] Update main and prepare dev for delete (#96) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix - paths to user model * feat - api version control * fix - conf morgan to ignore redirected req * docs - base controller * feat - calendar model * Squashed commit of the following: commit 3e0da0c16335e6d2679eda326290424f258807da Author: Muttaqin Date: Fri Aug 11 00:13:20 2023 +0600 Feat/token based authentication 2 (#91) * Move .husky to the base dir. * Delete eslintrc.json. * Resolve #71 & fix import paths for user_model . * token refresh & logout. * Add utility class auth_utils. * Configure cookie parser. commit d5ff9847b69c623c4f965ae09d80bcd173dc1169 Author: Abdelouahab Bella Date: Fri Jul 28 12:49:05 2023 +0100 fix - add logo path to readme (#86) commit 00c882e7ba7f9f1ca1b53163cef8c71888a04baf Author: Abdelouahab Bella Date: Fri Jul 28 12:47:07 2023 +0100 fix - eslint json roles -> js (#85) * Update access to env * configure rate limiter to ignore the SSE requests * verify user authorities and restrictions * update access to env variables * fix - adapt testing to new functionalities * fix - env variables security * extra fix * fix eslint json -> js commit f0674fb02a6d1d176e9e3892e1a22487a63a75d6 Author: Abdelouahab Bella Date: Fri Jul 28 11:29:11 2023 +0100 Update app_config.js (#84) commit b659237415acdd8496fe429fd4b5e587004a1c0b Author: Abdelouahab Bella Date: Fri Jul 28 11:21:01 2023 +0100 fix - Up date env variables : remove hard coded credentials (#83) * Update access to env * configure rate limiter to ignore the SSE requests * verify user authorities and restrictions * update access to env variables * fix - adapt testing to new functionalities * fix - env variables security commit af609a1977c50a2bb5514bb576861aa9545d6f67 Author: Abdelouahab Bella Date: Fri Jul 28 10:41:55 2023 +0100 Creating CONTRIBUTING.md (#79) commit 9254b1d5742ff094eced3ffafe714d0ba2f2a8a8 Author: Muttaqin Date: Fri Jul 28 00:10:13 2023 +0600 Feat/configure pre-commit hook (#81) * configure pre-commit hooks. * Pre-commit hook test - lint & fix every .js & .json file. commit fbb15378305b4a5d8422f6c4e4b59bafbcecd417 Author: Abdelouahab Bella Date: Wed Jul 26 17:47:48 2023 +0100 fix - rate limiter & duplicated methods & env variables (#74) * Update access to env * configure rate limiter to ignore the SSE requests * verify user authorities and restrictions * update access to env variables commit c1894bc4b8490c3da848d963ac79fac5b5603870 Author: Abdelouahab Bella Date: Tue Jul 25 19:59:30 2023 +0100 add count of closed pr (#76) commit 8cf1209fc3097d6c67c066e13d86fe8049fbb491 Author: Muttaqin Date: Wed Jul 26 00:38:42 2023 +0600 Feat - token-based-authentication (#75) * Configure linter and formatter. * Add configurations for access & refresh token. * Add token model. * Add functionality to generate Access & refresh token and send it to user. * save exact version of dev-dependencies. commit 0140aa1fdfd243d112af7ab2addb899b99112f4c Author: boujrada yassine <63125972+yassineboujrada@users.noreply.github.com> Date: Fri Jul 21 10:44:15 2023 +0100 Implement Sign Up Component with Next.js (#67) * adding a login page but it need a auth * adding sign up componenet commit e2227f9b5e463330de1424fdee717ba79748e935 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri Jul 21 09:46:10 2023 +0100 dependencies update #69 Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4. - [Release notes](https://github.com/jonschlinkert/word-wrap/releases) - [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4) --- updated-dependencies: - dependency-name: word-wrap dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 19b5bf9edaa07d8297864aa72e42a30fbc1568ae Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri Jul 21 09:45:40 2023 +0100 dependencies update #68 Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.10.4 to 6.11.3. - [Release notes](https://github.com/Automattic/mongoose/releases) - [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md) - [Commits](https://github.com/Automattic/mongoose/compare/6.10.4...6.11.3) --- updated-dependencies: - dependency-name: mongoose dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit c6c6d61da64be6035705120c29b446030f7812a6 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri Jul 21 09:44:40 2023 +0100 dependencies update #65 Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2) --- updated-dependencies: - dependency-name: semver dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit dd8c55a76982041f7f1c29c743476a45cc0baf17 Merge: e2503a6 9b899d0 Author: Abdelouahab Bella Date: Thu Jul 13 15:02:36 2023 +0100 Bump semver from 6.3.0 to 6.3.1 in /frontend-app #64 Bump semver from 6.3.0 to 6.3.1 in /frontend-app #64 commit e2503a690fc8193503ed901ed6e62a06646f4036 Merge: 372d6ce 26702c0 Author: Abdelouahab Bella Date: Thu Jul 13 15:01:06 2023 +0100 Github Oauth integration #58 Github Oauth integration #58 commit 9b899d06da2912f55a9a6533db513682c09837c0 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Jul 13 05:00:30 2023 +0000 Bump semver from 6.3.0 to 6.3.1 in /frontend-app Bumps [semver](https://github.com/npm/node-semver) from 6.3.0 to 6.3.1. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/v6.3.1/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v6.3.0...v6.3.1) --- updated-dependencies: - dependency-name: semver dependency-type: indirect ... Signed-off-by: dependabot[bot] commit 26702c0e90dfcf16d6c314b4896df606b33ff2ad Author: Muttaqin Date: Fri Jul 7 10:12:04 2023 +0600 Github integration. commit 372d6ce214626c80d68cc77814eed2f2fb0018f3 Merge: 55be937 92eddd6 Author: Abdelouahab Bella Date: Sat Jul 1 16:29:57 2023 +0100 Merge pull request #61 from bellaabdelouahab/dev Enhance - appError & code cleanup & login page in frontend [#61] commit 92eddd698160213d1c400e96c26f7dc450ef7c4c Merge: 2d3a5d3 d5b3b60 Author: Abdelouahab Bella Date: Sat Jul 1 16:26:43 2023 +0100 Merge commit 'd5b3b6057f88106099a3fd2a1095de400e767ff6' into dev commit 2d3a5d3d1a9589ba5be7dae0b43cb00c8fe41eef Merge: fbbdda1 55be937 Author: Abdelouahab Bella Date: Sat Jul 1 16:23:56 2023 +0100 Merge branch 'ISIL-ESTE:dev' into dev commit fbbdda19cde9ebda649595eeb2788d2a546e6702 Author: Abdelouahab Bella Date: Sat Jul 1 16:22:42 2023 +0100 enhe-app error clean up commit 55be93701b9cfac338a7af71872f44e306520962 Merge: a6fe803 dab86a2 Author: Abdelouahab Bella Date: Sat Jul 1 16:20:07 2023 +0100 Merge pull request #56 from bellaabdelouahab/dev Add user activation, metadata, routes, and soft delete logic; fix response path and server errors commit d5b3b6057f88106099a3fd2a1095de400e767ff6 Author: yassineboujrada Date: Wed Jun 28 23:29:42 2023 +0100 adding page for login with tailwind the part need is fix auth commit 1cdb4aed95444a7e63d722a24c2e3a096c27e0b4 Author: Abdelouahab Bella Date: Tue Jun 27 18:57:46 2023 +0100 fix errors and add testing for activation commit dab86a295694eb7a772da05d5d51a3440551d501 Author: Abdelouahab Bella Date: Mon Jun 26 19:49:14 2023 +0100 requested changes commit 41865dd5924d775952a30007e0c5ad60bdf1a31f Author: Abdelouahab Bella Date: Mon Jun 26 17:31:27 2023 +0100 Add user activation, metadata, routes, and soft delete logic; fix response path and server errors commit 8ebd6806c434514ce6e8e80f391bc88a500a7fac Merge: 710ccbf a6fe803 Author: Abdelouahab Bella Date: Sun Jun 25 21:56:09 2023 +0100 trying to merge commit a6fe8030092b238617f76e10db10ec727532a2c3 Merge: 0115fee 2c4d94c Author: Abdelouahab Bella Date: Sun Jun 25 21:49:23 2023 +0100 Merge pull request #53 from muttaqin1/refactor/file-names Refactor - #53 change file names commit 2c4d94c44b6c1b1d7c1cf2cd06899ed472aa22b0 Author: muttaqin1 Date: Mon Jun 26 02:18:51 2023 +0600 Update import paths. commit 7609c6b65a91c8c2256a52e29bb40d3d573f44b1 Author: muttaqin1 Date: Mon Jun 26 01:32:02 2023 +0600 Refactor file names with underscore. commit 392b611e20437744a0a19dd449f4214b536053cf Author: muttaqin1 Date: Mon Jun 26 01:08:59 2023 +0600 resolve #52. commit 710ccbf127d7b4375618fa08c84c6e56857bd8f1 Author: Abdelouahab Bella Date: Sun Jun 25 19:17:42 2023 +0100 requested changes commit 0115feed77b35b8d7fc7f3809daf83468f99831b Merge: dca09e4 c7b8464 Author: Khalid BOUSSAROUAL <102565973+Khalid1G@users.noreply.github.com> Date: Sun Jun 25 19:14:38 2023 +0100 Merge pull request #51 from bellaabdelouahab/dev fix - testing role managment commit c7b846431455dc92f3ec156c89a2fbf95b2b3da6 Author: Abdelouahab Bella Date: Sun Jun 25 12:43:24 2023 +0100 code cleaning commit 5b65a0fe3c76fa55b470b78d216b7350013275a0 Merge: b2aa675 dca09e4 Author: Abdelouahab Bella Date: Sun Jun 25 12:21:08 2023 +0100 Merge remote-tracking commit b2aa6754de71c6f22aa42d691ba5217631213f7a Author: Abdelouahab Bella Date: Sun Jun 25 11:52:49 2023 +0100 👌fix-swagger-autogen & endpoints & jwt,feat-tests commit dca09e45fae3d88804066a363256477d222af30d Merge: a97eeea 154d7ee Author: Abdelouahab Bella Date: Fri Jun 23 17:10:00 2023 +0100 Merge pull request #45 from muttaqin1/bugFix/ban-user [#45 ] issue - Bug fix/ban user commit 154d7eeec82cf1b15f1d02aa3c20fb0eda6da183 Merge: 8d857ee a97eeea Author: Abdelouahab Bella Date: Fri Jun 23 16:58:37 2023 +0100 Merge branch 'dev' into bugFix/ban-user commit 8d857eec2ec4f157f74f56c8ab9bf0d4d91fe4fe Author: muttaqin1 Date: Fri Jun 23 21:34:32 2023 +0600 Fix ban and unban user. commit a97eeead0f74581929c1ce77d1545e3d8ce7fae0 Merge: d9ae2c6 17e53a5 Author: Khalid BOUSSAROUAL <102565973+Khalid1G@users.noreply.github.com> Date: Thu Jun 22 15:41:41 2023 +0100 Merge pull request #40 from bellaabdelouahab/dev test - start code testing for login and sign up commit 17e53a50c03afd3e9775e9f790839b50b686afa4 Merge: be73f7d d9ae2c6 Author: Khalid BOUSSAROUAL <102565973+Khalid1G@users.noreply.github.com> Date: Thu Jun 22 15:39:57 2023 +0100 Merge branch 'dev' into dev commit d9ae2c6352ab59983ee1588efed8d0b9d6cc0191 Merge: 9d0bdd6 d6ebb36 Author: Khalid BOUSSAROUAL <102565973+Khalid1G@users.noreply.github.com> Date: Thu Jun 22 15:31:44 2023 +0100 Merge pull request #38 from ISIL-ESTE/limiter add rate limit commit be73f7d202c9e4e80dfed0b4446d51bc65314ae7 Author: Abdelouahab Bella Date: Thu Jun 22 14:58:43 2023 +0100 test - add basic example of testing & fix imports commit d6ebb363c501be12c378530266be7ef198da4436 Author: Khalid BOUSSAROUAL <102565973+Khalid1G@users.noreply.github.com> Date: Thu Jun 22 13:46:01 2023 +0100 add rate limit commit 70bbe22766427aa34a72669690939a778e00bc37 Author: Abdelouahab Bella Date: Thu Jun 22 11:33:17 2023 +0100 👌 ensuring that main branch is protected commit 7301c3be4918eac9f9e2c6b1d7769ec70e94954c Author: Abdelouahab Bella Date: Thu Jun 22 11:27:28 2023 +0100 👌ensuring that Conterbute is working --- .husky/pre-commit | 5 + .husky/pre-push | 5 + backend-app/.env.example | 2 +- backend-app/.eslintrc.js | 3 +- backend-app/app.js | 26 ++-- backend-app/config/app_config.js | 21 +-- backend-app/constants/meta_data.js | 4 +- backend-app/controllers/admin_controller.js | 2 +- backend-app/controllers/auth_controller.js | 131 +++++++++++------- backend-app/controllers/base_controller.js | 5 +- backend-app/controllers/github_controller.js | 38 +++-- backend-app/controllers/user_controller.js | 2 +- .../middlewares/api_version_controll.js | 24 ++++ .../middlewares/global_error_handler.js | 1 - backend-app/middlewares/morgan.js | 2 +- backend-app/models/calendar/calendar_model.js | 119 ++++++++++++---- backend-app/models/calendar/event_model.js | 53 +++++++ backend-app/models/user/user_model.js | 8 +- backend-app/package-lock.json | 21 +++ backend-app/package.json | 5 +- backend-app/routes/auth_routes.js | 2 + backend-app/tests/e2e/login-signup.test.js | 2 +- backend-app/utils/authorization/auth_utils.js | 57 ++++++++ .../utils/authorization/role/create_roles.js | 11 +- backend-app/utils/create_default_user.js | 4 +- backend-app/utils/searchCookie.js | 9 ++ 26 files changed, 428 insertions(+), 134 deletions(-) create mode 100755 .husky/pre-commit create mode 100755 .husky/pre-push create mode 100644 backend-app/middlewares/api_version_controll.js create mode 100644 backend-app/utils/authorization/auth_utils.js create mode 100644 backend-app/utils/searchCookie.js diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..9d1980c --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +. "$(dirname "$0")/_/husky.sh" + +cd backend-app +npx lint-staged diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..5d6e8f4 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +. "$(dirname "$0")/_/husky.sh" + +cd backend-app +jest diff --git a/backend-app/.env.example b/backend-app/.env.example index 3dbc9a9..62d8bfb 100644 --- a/backend-app/.env.example +++ b/backend-app/.env.example @@ -13,4 +13,4 @@ REQUIRE_ACTIVATION = false RATE_LIMIT_PER_HOUR = 500 GITHUB_OAUTH_CLIENT_ID = "Iv1.6f4b4b8b0b1b4b8b" GITHUB_OAUTH_CLIENT_SECRET = "6f4b4b8b0b1b4b8b6f4b4b8b0b1b4b8b" -GITHUB_OAUTH_REDIRECT_URL = "http://localhost:3000/auth/github/callback" \ No newline at end of file +GITHUB_OAUTH_REDIRECT_URL = "http://localhost:3000/auth/github/callback" diff --git a/backend-app/.eslintrc.js b/backend-app/.eslintrc.js index e3ee8c4..93357af 100644 --- a/backend-app/.eslintrc.js +++ b/backend-app/.eslintrc.js @@ -7,7 +7,7 @@ module.exports = { 'block-scoped-var': 'error', 'class-methods-use-this': 'error', complexity: ['error', 10], - 'consistent-return': 'error', + 'consistent-return': 'off', 'default-case': 'error', 'dot-location': ['error', 'property'], 'dot-notation': 'error', @@ -69,6 +69,7 @@ module.exports = { yoda: 'error', 'no-console': 'warn', 'no-var': 'error', + 'no-undef': 'off', 'no-unused-vars': 'warn', 'arrow-body-style': ['error', 'as-needed'], }, diff --git a/backend-app/app.js b/backend-app/app.js index 9c2cc32..c164361 100644 --- a/backend-app/app.js +++ b/backend-app/app.js @@ -9,8 +9,14 @@ const xss = require('xss-clean'); const hpp = require('hpp'); const cors = require('cors'); const morgan = require('./middlewares/morgan'); +const setDefaultAPIVersion = require('./middlewares/api_version_controll'); const swaggerDocs = require('./utils/swagger'); -const { CURRENT_ENV, API_VERSION } = require('./config/app_config'); +const { + COOKIE_SECRET, + CURRENT_ENV, + API_VERSION, +} = require('./config/app_config'); +const cookieParser = require('cookie-parser'); const app = express(); @@ -19,6 +25,8 @@ swaggerDocs(app); // use json as default format app.use(express.json()); +//configure cookie parser +app.use(cookieParser(COOKIE_SECRET)); // use morgan for logging app.use(morgan); @@ -59,17 +67,8 @@ if (CURRENT_ENV === 'production') { app.use(limiter); } -// check if no version is provided if so use the default version -// example api/auth/user/signup => api/v1/auth/user/signup -app.use((req, res, next) => { - if (req.originalUrl.startsWith('/api')) { - req.originalUrl = `/api/${API_VERSION}${req.originalUrl}`; - } - next(); -}); - -// routes -app.use(`/api/${API_VERSION}`, require('./routes/index')); +// if no version is specified, use the default version +app.use(setDefaultAPIVersion); app.get('/', (req, res) => { res.status(200).json({ @@ -79,6 +78,9 @@ app.get('/', (req, res) => { }); }); +// routes +app.use(`/api`, require('./routes/index')); + // handle undefined Routes app.use('*', (req, res, next) => { const err = new AppError(404, 'fail', 'Route Not Found', req.originalUrl); diff --git a/backend-app/config/app_config.js b/backend-app/config/app_config.js index 7ef0523..3f0c132 100644 --- a/backend-app/config/app_config.js +++ b/backend-app/config/app_config.js @@ -7,14 +7,14 @@ const envFile = fs.existsSync('.env') ? '.env' : '.env.example'; dotenv.config({ path: join(__dirname, `../${envFile}`) }); exports.logFilePath = join(__dirname, '../server-logs'); -exports.CURRENT_ENV = process.env.NODE_ENV ?.toLowerCase(); -exports.API_VERSION = process.env.API_VERSION ; -exports.DATABASE = process.env.MONGO_URI ; -exports.PORT = process.env.PORT ; -exports.ADMIN_EMAIL = process.env.ADMIN_EMAIL ; -exports.ADMIN_PASSWORD = process.env.ADMIN_PASSWORD ; -exports.REQUIRE_ACTIVATION = process.env.REQUIRE_ACTIVATION ; -exports.RATE_LIMIT_PER_HOUR = process.env.RATE_LIMIT_PER_HOUR ; +exports.CURRENT_ENV = process.env.NODE_ENV?.toLowerCase(); +exports.API_VERSION = process.env.API_VERSION; +exports.DATABASE = process.env.MONGO_URI; +exports.PORT = process.env.PORT; +exports.ADMIN_EMAIL = process.env.ADMIN_EMAIL; +exports.ADMIN_PASSWORD = process.env.ADMIN_PASSWORD; +exports.REQUIRE_ACTIVATION = process.env.REQUIRE_ACTIVATION; +exports.RATE_LIMIT_PER_HOUR = process.env.RATE_LIMIT_PER_HOUR; exports.GITHUB_OAUTH_CLIENT_ID = process.env.GITHUB_OAUTH_CLIENT_ID; exports.GITHUB_OAUTH_CLIENT_SECRET = process.env.GITHUB_OAUTH_CLIENT_SECRET; exports.GITHUB_OAUTH_REDIRECT_URL = process.env.GITHUB_OAUTH_REDIRECT_URL; @@ -22,3 +22,8 @@ exports.ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET; exports.ACCESS_TOKEN_EXPIRY_TIME = process.env.ACCESS_TOKEN_EXPIRY_TIME; exports.REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET; exports.REFRESH_TOKEN_EXPIRY_TIME = process.env.REFRESH_TOKEN_EXPIRY_TIME; +exports.ACCESS_TOKEN_COOKIE_EXPIRY_TIME = + process.env.ACCESS_TOKEN_COOKIE_EXPIRY_TIME; +exports.REFRESH_TOKEN_COOKIE_EXPIRY_TIME = + process.env.REFRESH_TOKEN_COOKIE_EXPIRY_TIME; +exports.COOKIE_SECRET = process.env.COOKIE_SECRET; diff --git a/backend-app/constants/meta_data.js b/backend-app/constants/meta_data.js index c79889b..e74575a 100644 --- a/backend-app/constants/meta_data.js +++ b/backend-app/constants/meta_data.js @@ -1,4 +1,4 @@ -const enableMetaData = (model) => { +const apply = (model) => { model.add({ deleted: { type: Boolean, @@ -20,4 +20,4 @@ const enableMetaData = (model) => { }); }; -exports.enableMetaData = enableMetaData; +exports.apply = apply; diff --git a/backend-app/controllers/admin_controller.js b/backend-app/controllers/admin_controller.js index e32eefe..ced2918 100644 --- a/backend-app/controllers/admin_controller.js +++ b/backend-app/controllers/admin_controller.js @@ -1,4 +1,4 @@ -const userModel = require('../models/user_model'); +const userModel = require('../models/user/user_model'); const Actions = require('../constants/actions'); const validateActions = require('../utils/authorization/validate_actions'); const Role = require('../utils/authorization/role/role'); diff --git a/backend-app/controllers/auth_controller.js b/backend-app/controllers/auth_controller.js index 4ebb19a..bd304b5 100644 --- a/backend-app/controllers/auth_controller.js +++ b/backend-app/controllers/auth_controller.js @@ -1,21 +1,17 @@ const { promisify } = require('util'); const mongoose = require('mongoose'); -const jwt = require('jsonwebtoken'); -const User = require('../models/user_model'); +const User = require('../models/user/user_model'); const AppError = require('../utils/app_error'); const Role = require('../utils/authorization/role/role'); -const { - ACCESS_TOKEN_SECRET, - REQUIRE_ACTIVATION, -} = require('../config/app_config'); +const { REQUIRE_ACTIVATION } = require('../config/app_config'); const { getGithubOAuthUser, getGithubOAuthToken, getGithubOAuthUserPrimaryEmail, } = require('../utils/authorization/github'); -const TokenModel = require('../models/token_model'); const role = new Role(); -const generateTokens = require('../utils/authorization/generateTokens'); +const AuthUtils = require('../utils/authorization/auth_utils'); +const searchCookies = require('../utils/searchCookie'); const generateActivationKey = async () => { const randomBytesPromiseified = promisify(require('crypto').randomBytes); @@ -26,17 +22,23 @@ const generateActivationKey = async () => { exports.githubHandler = async (req, res, next) => { try { const Roles = await role.getRoles(); - const { code } = req.query; + const { code, redirect_url } = req.query; + if (!redirect_url) + throw new AppError(400, 'fail', 'Please provide redirect_url'); if (!code) throw new AppError(400, 'fail', 'Please provide code'); const { access_token } = await getGithubOAuthToken(code); if (!access_token) throw new AppError(400, 'fail', 'Invalid code'); const githubUser = await getGithubOAuthUser(access_token); const primaryEmail = await getGithubOAuthUserPrimaryEmail(access_token); const exists = await User.findOne({ email: primaryEmail }); - if (exists) - return res.status(200).json({ - token: await generateTokens(exists._id), - }); + if (exists) { + const accessToken = AuthUtils.generateAccessToken(exists._id); + const refreshToken = AuthUtils.generateRefreshToken(exists._id); + AuthUtils.setAccessTokenCookie( + res, + accessToken + ).setRefreshTokenCookie(res, refreshToken); + } if (!githubUser) throw new AppError(400, 'fail', 'Invalid access token'); const createdUser = await User.create({ @@ -50,11 +52,15 @@ exports.githubHandler = async (req, res, next) => { githubOauthAccessToken: access_token, active: true, }); - const tokens = await generateTokens(createdUser._id); - res.status(201).json({ - user: createdUser, - tokens, - }); + + const accessToken = AuthUtils.generateAccessToken(createdUser._id); + const refreshToken = AuthUtils.generateRefreshToken(createdUser._id); + AuthUtils.setAccessTokenCookie(res, accessToken).setRefreshTokenCookie( + res, + refreshToken + ); + //redirect user to redirect url + res.redirect(redirect_url); } catch (err) { next(err); } @@ -88,14 +94,18 @@ exports.login = async (req, res, next) => { ); } - // 3) All correct, send jwt to client - const tokens = await generateTokens(user._id); + // 3) All correct, send accessToken & refreshToken to client via cookie + const accessToken = AuthUtils.generateAccessToken(user._id); + const refreshToken = AuthUtils.generateRefreshToken(user._id); + AuthUtils.setAccessTokenCookie(res, accessToken).setRefreshTokenCookie( + res, + refreshToken + ); // Remove the password from the output user.password = undefined; res.status(200).json({ - tokens: tokens, data: { user, }, @@ -118,8 +128,13 @@ exports.signup = async (req, res, next) => { restrictions: Roles.USER.restrictions, ...(REQUIRE_ACTIVATION && { activationKey }), }); - const tokens = await generateTokens(user._id); + const accessToken = AuthUtils.generateAccessToken(user._id); + const refreshToken = AuthUtils.generateRefreshToken(user._id); + AuthUtils.setAccessTokenCookie(res, accessToken).setRefreshTokenCookie( + res, + refreshToken + ); // Remove the password and activation key from the output user.password = undefined; user.activationKey = undefined; @@ -135,7 +150,42 @@ exports.signup = async (req, res, next) => { }, }); } catch (err) { - console.log(err); + next(err); + } +}; + +exports.tokenRefresh = async (req, res, next) => { + try { + const refreshToken = searchCookies(req, 'refresh_token'); + if (!refreshToken) + throw new AppError(400, 'fail', 'You have to login to continue.'); + const refreshTokenPayload = await AuthUtils.verifyRefreshToken( + refreshToken + ); + if (!refreshTokenPayload || !refreshTokenPayload.id) + throw new AppError(400, 'fail', 'Invalid refresh token'); + const user = await User.findById(refreshTokenPayload.id); + if (!user) throw new AppError(400, 'fail', 'Invalid refresh token'); + const accessToken = AuthUtils.generateAccessToken(user._id); + //set or override accessToken cookie. + AuthUtils.setAccessTokenCookie(res, accessToken); + res.sendStatus(204); + } catch (err) { + next(err); + } +}; +exports.logout = async (req, res, next) => { + try { + const accessToken = searchCookies(req, 'access_token'); + if (!accessToken) + throw new AppError(400, 'fail', 'Please provide access token'); + const accessTokenPayload = await AuthUtils.verifyAccessToken( + accessToken + ); + if (!accessTokenPayload || !accessTokenPayload.id) + throw new AppError(400, 'fail', 'Invalid access token'); + res.sendStatus(204); + } catch (err) { next(err); } }; @@ -247,6 +297,7 @@ exports.forgotPassword = async (req, res, next) => { ); // send email with reset key + // eslint-disable-next-line no-warning-comments // TODO: send email with reset key res.status(200).json({ @@ -259,29 +310,17 @@ exports.forgotPassword = async (req, res, next) => { exports.protect = async (req, res, next) => { try { - // 1) check if the token is there - let token; - if ( - req.headers.authorization && - req.headers.authorization.startsWith('Bearer') - ) { - token = req.headers.authorization.split(' ')[1]; - } - if (!token) { - return next( - new AppError( - 401, - 'fail', - 'You are not logged in! Please login in to continue' - ) - ); - } - - // 2) Verify token - const decode = await promisify(jwt.verify)(token, ACCESS_TOKEN_SECRET); + const accessToken = searchCookies(req, 'access_token'); + if (!accessToken) + return next(new AppError(401, 'fail', 'Please login to continue')); + const accessTokenPayload = await AuthUtils.verifyAccessToken( + accessToken + ); + if (!accessTokenPayload || !accessTokenPayload.id) + throw new AppError(401, 'fail', 'Invalid access token'); // 3) check if the user is exist (not deleted) - const user = await User.findById(decode.id).select( + const user = await User.findById(accessTokenPayload.id).select( '+githubOauthAccessToken' ); if (!user) { @@ -289,9 +328,6 @@ exports.protect = async (req, res, next) => { new AppError(401, 'fail', 'This user is no longer exist') ); } - const tokenRecord = await TokenModel.findOne({ userId: user._id }); - if (!tokenRecord) - throw new AppError(401, 'fail', 'Invalid Access Token'); // Check if the account is banned if (user?.accessRestricted) @@ -303,7 +339,6 @@ exports.protect = async (req, res, next) => { ) ); req.user = user; - req.token = tokenRecord; // check if account is active if (!user.active) return next( diff --git a/backend-app/controllers/base_controller.js b/backend-app/controllers/base_controller.js index c62bc3c..0221f43 100644 --- a/backend-app/controllers/base_controller.js +++ b/backend-app/controllers/base_controller.js @@ -2,7 +2,7 @@ const AppError = require('../utils/app_error'); const APIFeatures = require('../utils/api_features'); /** - * Delete a document by ID + * Delete a document by ID (soft delete) * @param {Model} Model - The mongoose model * @returns {Function} - Express middleware function */ @@ -66,6 +66,9 @@ exports.updateOne = (Model) => async (req, res, next) => { */ exports.createOne = (Model) => async (req, res, next) => { try { + // get the user who is creating the document + const userid = req.user._id; + req.body.createdBy = userid; const doc = await Model.create(req.body); res.status(201).json({ diff --git a/backend-app/controllers/github_controller.js b/backend-app/controllers/github_controller.js index ae10538..da4b377 100644 --- a/backend-app/controllers/github_controller.js +++ b/backend-app/controllers/github_controller.js @@ -1,4 +1,4 @@ -const User = require('../models/user_model'); +const User = require('../models/user/user_model'); const axios = require('axios'); const AppError = require('../utils/app_error'); @@ -14,28 +14,26 @@ exports.getRecentRepo = async (req, res, next) => { } ); const mappedUserRepositories = userRepositories.data.map( - (repository) => { - return { - id: repository.id, - name: repository.name, - full_name: repository.full_name, - description: repository.description, - isFork: repository.fork, - language: repository.language, - license: repository.license?.name - ? repository.license.name - : null, - openedIssuesCount: repository.open_issues_count, - repoCreatedAt: repository.created_at, - url: repository.url, - }; - } + (repository) => ({ + id: repository.id, + name: repository.name, + full_name: repository.full_name, + description: repository.description, + isFork: repository.fork, + language: repository.language, + license: repository.license?.name + ? repository.license.name + : null, + openedIssuesCount: repository.open_issues_count, + repoCreatedAt: repository.created_at, + url: repository.url, + }) ); if (mappedUserRepositories.length <= 0) throw new AppError(400, 'fail', 'No repositories found'); - const sortedRepository = mappedUserRepositories.sort((a, b) => { - return new Date(b.repoCreatedAt) - new Date(a.repoCreatedAt); - }); + const sortedRepository = mappedUserRepositories.sort( + (a, b) => new Date(b.repoCreatedAt) - new Date(a.repoCreatedAt) + ); const recentRepository = sortedRepository[0]; res.status(200).json({ diff --git a/backend-app/controllers/user_controller.js b/backend-app/controllers/user_controller.js index ee8cb8a..296cedd 100644 --- a/backend-app/controllers/user_controller.js +++ b/backend-app/controllers/user_controller.js @@ -1,4 +1,4 @@ -const User = require('../models/user_model'); +const User = require('../models/user/user_model'); const base = require('./base_controller'); const AppError = require('../utils/app_error'); diff --git a/backend-app/middlewares/api_version_controll.js b/backend-app/middlewares/api_version_controll.js new file mode 100644 index 0000000..ce004bd --- /dev/null +++ b/backend-app/middlewares/api_version_controll.js @@ -0,0 +1,24 @@ +const { API_VERSION } = require('../config/app_config'); + +/** + * Middleware to set default API version in the request URL if not provided. + * If the API version is missing, it will be set to 'v3' by default. + * @param {Object} req - Express request object. + * @param {Object} res - Express response object. + * @param {Function} next - Express next function to pass control to the next middleware or route handler. + */ +const setDefaultAPIVersion = (req, res, next) => { + // Check if the request URL contains the API version (e.g., /api/v2/some-endpoint) + const apiVersion = req.url.match(/\/api\/v(\d+)/); + if (!apiVersion) { + const url = req.url.replace('/api', ''); + req.url = `/api/${API_VERSION}${url}`; + res.redirect(req.url) + return + } + else + next(); +}; + + +module.exports = setDefaultAPIVersion; \ No newline at end of file diff --git a/backend-app/middlewares/global_error_handler.js b/backend-app/middlewares/global_error_handler.js index 702c2e2..f979452 100644 --- a/backend-app/middlewares/global_error_handler.js +++ b/backend-app/middlewares/global_error_handler.js @@ -1,7 +1,6 @@ const httpStatus = require('http-status-codes'); const { CURRENT_ENV } = require('../config/app_config'); const AppError = require('../utils/app_error'); -const { Logger } = require('winston'); require('../utils/logger'); /** diff --git a/backend-app/middlewares/morgan.js b/backend-app/middlewares/morgan.js index 7f2085f..1fb40d8 100644 --- a/backend-app/middlewares/morgan.js +++ b/backend-app/middlewares/morgan.js @@ -12,7 +12,7 @@ const stream = { // Setup the logger const Morgan = morgan( ':method :url :status :res[content-length] - :response-time ms', - { stream, skip: () => CURRENT_ENV.toLowerCase() === 'production' } + { stream, skip: (req, res) => CURRENT_ENV.toLowerCase() === 'production' || (req.originalUrl && req.originalUrl !== req.url) } ); // Export the logger diff --git a/backend-app/models/calendar/calendar_model.js b/backend-app/models/calendar/calendar_model.js index 327a75b..3f0d00e 100644 --- a/backend-app/models/calendar/calendar_model.js +++ b/backend-app/models/calendar/calendar_model.js @@ -1,27 +1,92 @@ -/* -| - calendarId: int | -| - calendarName: string | -| - calendarType: CalendarType | -| - isPublic: bool | -| - isShared: bool | -| - owner: User | -| - participants: List | -| - events: List | -| - reminders: List | -| - permissions: List | -| - creationDate: DateTime | -| - createdBy: User | -| - lastModifiedDate: DateTime | -| - lastModifiedBy: User | -| - description: string | -| - accessCode: string | -| - privacySettings: PrivacySettings | -| - isSyncEnabled: bool | -| - attachment: byte[] | -| - tags: List | -| - categories: List | -| - permissions: List | -| - allowedUsers: List | -| - deniedUsers: List | -// option to add an event to google calendar -*/ \ No newline at end of file + + +const mongoose = require('mongoose'); +const validator = require('validator'); +const metaData = require('../constants/meta_data'); + +const calendarSchema = new mongoose.Schema( + { + calendarName: { + type: String, + required: [true, 'Please fill your calendar name'], + }, + calendarType: { + type: String, + required: [true, 'Please fill your calendar type'], + validate: { + validator: function (el) { + return el === 'Personal' || el === 'Group'; + } + } + }, + isPublic: { + type: Boolean, + default: false, + }, + isShared: { + type: Boolean, + default: false, + }, + participants: [ + { + type: mongoose.Schema.ObjectId, + ref: 'User', + }, + ], + events: [ + { + type: mongoose.Schema.ObjectId, + ref: 'Event', + }, + ], + // reminders: [ + // { + // type: mongoose.Schema.ObjectId, + // ref: 'Reminder', + // }, + // ], + creationDate: { + type: Date, + default: Date.now(), + }, + description: { + type: String, + }, + accessCode: { + type: String, + }, + // attachment: { + // type: Buffer, + // }, + tags: [ + { + type: String, + }, + ], + // categories: [ + // { + // type: mongoose.Schema.ObjectId, + // ref: 'Category', + // }, + // ], + allowedUsers: [ + { + type: mongoose.Schema.ObjectId, + ref: 'User', + }, + ], + deniedUsers: [ + { + type: mongoose.Schema.ObjectId, + ref: 'User', + }, + ], + }, + { + toJSON: { virtuals: true }, + toObject: { virtuals: true }, + } +); + +metaData.apply(calendarSchema); + diff --git a/backend-app/models/calendar/event_model.js b/backend-app/models/calendar/event_model.js index e69de29..ae98932 100644 --- a/backend-app/models/calendar/event_model.js +++ b/backend-app/models/calendar/event_model.js @@ -0,0 +1,53 @@ + +const mongoose = require('mongoose'); +const validator = require('validator'); +const metaData = require('../constants/meta_data'); + +const eventSchema = new mongoose.Schema( + { + eventName: { + type: String, + required: [true, 'Please fill your event name'], + }, + eventDescription: { + type: String, + }, + eventLocation: { + type: String, + }, + eventStartDate: { + type: Date, + required: [true, 'Please fill your event start date'], + }, + eventEndDate: { + type: Date, + required: [true, 'Please fill your event end date'], + }, + eventStartTime: { + type: String, + required: [true, 'Please fill your event start time'], + }, + eventEndTime: { + type: String, + required: [true, 'Please fill your event end time'], + }, + eventColor: { + type: String, + required: [true, 'Please fill your event color'], + }, + eventRecurring: { + type: Boolean, + default: false, + }, + eventRecurringType: { + type: String, + enum: ['Daily', 'Weekly', 'Monthly', 'Yearly'], + }, + eventRecurringEndDate: { + type: Date, + }, + eventRecurringTime: { + type: String, + }, + }) + diff --git a/backend-app/models/user/user_model.js b/backend-app/models/user/user_model.js index da07199..5f5ac3b 100644 --- a/backend-app/models/user/user_model.js +++ b/backend-app/models/user/user_model.js @@ -1,9 +1,9 @@ const mongoose = require('mongoose'); const validator = require('validator'); const bcrypt = require('bcryptjs'); -const Actions = require('../constants/actions'); -const metaData = require('../constants/meta_data'); -const { REQUIRE_ACTIVATION } = require('../config/app_config'); +const Actions = require('../../constants/actions'); +const metaData = require('../../constants/meta_data'); +const { REQUIRE_ACTIVATION } = require('../../config/app_config'); const userSchema = new mongoose.Schema( { @@ -77,7 +77,7 @@ const userSchema = new mongoose.Schema( ); // add meta data to the schema -metaData.enableMetaData(userSchema); +metaData.apply(userSchema); userSchema.pre('save', async function (next) { if ( diff --git a/backend-app/package-lock.json b/backend-app/package-lock.json index 008fc91..bba55b5 100644 --- a/backend-app/package-lock.json +++ b/backend-app/package-lock.json @@ -12,6 +12,7 @@ "axios": "^1.4.0", "bcryptjs": "^2.4.3", "compression": "^1.7.4", + "cookie-parser": "1.4.6", "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", @@ -3387,6 +3388,26 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", diff --git a/backend-app/package.json b/backend-app/package.json index 288429e..0e999a8 100644 --- a/backend-app/package.json +++ b/backend-app/package.json @@ -5,11 +5,11 @@ "main": "app.js", "scripts": { "debug": "ndb server.js", - "dev": "nodemon server.js NODE_ENV=DEVELOPMENT ", + "dev": " NODE_ENV=DEVELOPMENT nodemon server.js", "prod": "node server.js NODE_ENV=PRODUCTION", "start": "nodemon server.js", "test": "jest", - "prepare": "cd .. && husky install backend-app/.husky" + "prepare": "cd .. && husky install" }, "author": "Bella Abdelouahab", "license": "ISC", @@ -17,6 +17,7 @@ "axios": "^1.4.0", "bcryptjs": "^2.4.3", "compression": "^1.7.4", + "cookie-parser": "1.4.6", "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", diff --git a/backend-app/routes/auth_routes.js b/backend-app/routes/auth_routes.js index 45dcca5..c0f4afb 100644 --- a/backend-app/routes/auth_routes.js +++ b/backend-app/routes/auth_routes.js @@ -5,6 +5,8 @@ const router = express.Router(); router.post('/signup', authController.signup); router.post('/login', authController.login); +router.delete('/logout', authController.logout); +router.put('/token-refresh', authController.tokenRefresh); router.get('/activate', authController.activateAccount); router.patch('/updateMyPassword', authController.updatePassword); router.patch('/forgotPassword', authController.forgotPassword); diff --git a/backend-app/tests/e2e/login-signup.test.js b/backend-app/tests/e2e/login-signup.test.js index 15907f2..38ddf23 100644 --- a/backend-app/tests/e2e/login-signup.test.js +++ b/backend-app/tests/e2e/login-signup.test.js @@ -6,7 +6,7 @@ const app = require('../../app'); const createRoles = require('../../utils/authorization/role/create_roles'); const createDefaultUser = require('../../utils/create_default_user'); const { REQUIRE_ACTIVATION } = require('../../config/app_config'); -const user_model = require('../../models/user_model'); +const user_model = require('../../models/user/user_model'); const { log } = require('winston'); beforeAll(async () => { mongoose.set("strictQuery", false); diff --git a/backend-app/utils/authorization/auth_utils.js b/backend-app/utils/authorization/auth_utils.js new file mode 100644 index 0000000..79b92b5 --- /dev/null +++ b/backend-app/utils/authorization/auth_utils.js @@ -0,0 +1,57 @@ +const { + ACCESS_TOKEN_SECRET, + ACCESS_TOKEN_EXPIRY_TIME, + REFRESH_TOKEN_SECRET, + REFRESH_TOKEN_EXPIRY_TIME, + ACCESS_TOKEN_COOKIE_EXPIRY_TIME, + REFRESH_TOKEN_COOKIE_EXPIRY_TIME, +} = require('../../config/app_config'); +const AppError = require('../app_error'); +const jwt = require('jsonwebtoken'); +const { promisify } = require('util'); + +class AuthUtils { + static generateAccessToken(id) { + return jwt.sign({ id }, ACCESS_TOKEN_SECRET, { + expiresIn: ACCESS_TOKEN_EXPIRY_TIME, + }); + } + static generateRefreshToken(id) { + return jwt.sign({ id }, REFRESH_TOKEN_SECRET, { + expiresIn: REFRESH_TOKEN_EXPIRY_TIME, + }); + } + static async verifyAccessToken(token) { + try { + return await promisify(jwt.verify)(token, ACCESS_TOKEN_SECRET); + } catch (error) { + throw new AppError(401, 'fail', 'Invalid token'); + } + } + static async verifyRefreshToken(token) { + try { + return await promisify(jwt.verify)(token, REFRESH_TOKEN_SECRET); + } catch (error) { + throw new AppError(401, 'fail', 'Invalid token'); + } + } + static setAccessTokenCookie(res, accessToken) { + res.cookie('access_token', accessToken, { + httpOnly: true, + secure: true, + sameSite: 'strict', + maxAge: ACCESS_TOKEN_COOKIE_EXPIRY_TIME, + }); + return this; + } + static setRefreshTokenCookie(res, refreshToken) { + res.cookie('refresh_token', refreshToken, { + httpOnly: true, + secure: true, + sameSite: 'strict', + maxAge: REFRESH_TOKEN_COOKIE_EXPIRY_TIME, + }); + return this; + } +} +module.exports = AuthUtils; diff --git a/backend-app/utils/authorization/role/create_roles.js b/backend-app/utils/authorization/role/create_roles.js index 304b0f3..230a0d4 100644 --- a/backend-app/utils/authorization/role/create_roles.js +++ b/backend-app/utils/authorization/role/create_roles.js @@ -19,8 +19,17 @@ const user = { const createRoles = async () => { const role = new Role(); + const roleArr = Object.keys(await role.getRoles()); try { - await role.deleteDefaultRoles(); + if ( + roleArr.length > 2 && + roleArr.includes(superAdmin.type) && + roleArr.includes(admin.type) && + roleArr.includes(user.type) + ) { + Logger.info('DEFAULT ROLE [ADMIN, SUPER_ADMIN, USER] DETECTED!'); + return; + } await role.createRole( superAdmin.type, superAdmin.authorities, diff --git a/backend-app/utils/create_default_user.js b/backend-app/utils/create_default_user.js index 7e22ee1..7fab748 100644 --- a/backend-app/utils/create_default_user.js +++ b/backend-app/utils/create_default_user.js @@ -1,6 +1,6 @@ // create admin user if not exists -const User = require('../models/user_model'); +const User = require('../models/user/user_model'); const { ADMIN_EMAIL, ADMIN_PASSWORD } = require('../config/app_config'); const { SUPER_ADMIN } = require('../constants/default_roles'); @@ -19,7 +19,7 @@ const createAdminUser = async () => { }); } } catch (err) { - Logger.error(err); + Logger.error(err.stack); } }; diff --git a/backend-app/utils/searchCookie.js b/backend-app/utils/searchCookie.js new file mode 100644 index 0000000..6e9532e --- /dev/null +++ b/backend-app/utils/searchCookie.js @@ -0,0 +1,9 @@ +const searchCookies = (req, cookieName) => { + const cookies = + Object.keys(req.signedCookies).length > 0 ? req.signedCookies : false; + if (!cookies) return false; + const cookie = cookies[cookieName]; + if (!cookie) return false; + return cookie; +}; +module.exports = searchCookies;