diff --git a/.env.example b/.env.example index 32e2e92..3c94a31 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,9 @@ # Port number PORT=3000 +# Client URL +CLIENT_URL=http://localhost:5173,http://localhost:5174 + # URL of the Mongo DB MONGODB_URL=mongodb://127.0.0.1:27017/node-express-server @@ -23,3 +26,10 @@ SMTP_PORT=587 SMTP_USERNAME=email-server-username SMTP_PASSWORD=email-server-password EMAIL_FROM=support@yourapp.com + +# Google OAuth +# You can get the client ID and secret from the Google Developers Console: https://console.developers.google.com/ +# Use the same port as the one used in the `PORT` environment variable. By default, the callback URL is set to `http://localhost:3000/v1/auth/google/callback` +GOOGLE_CLIENT_ID=your_google_client_id +GOOGLE_CLIENT_SECRET=your_google_client_secret +GOOGLE_CALLBACK_URL=http://localhost:3000/v1/auth/google/callback \ No newline at end of file diff --git a/package.json b/package.json index 30311c3..a031a8b 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@faker-js/faker": "^8.4.1", "bcryptjs": "^2.4.3", "compression": "^1.7.4", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "coveralls": "^3.1.1", "cross-env": "^7.0.0", @@ -64,6 +65,7 @@ "morgan": "^1.9.1", "nodemailer": "^6.3.1", "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.0", "pm2": "^5.1.0", "swagger-jsdoc": "^6.0.8", diff --git a/src/app.js b/src/app.js index d0dab1d..8505a88 100644 --- a/src/app.js +++ b/src/app.js @@ -8,14 +8,17 @@ const passport = require('passport'); const httpStatus = require('http-status'); const config = require('./config/config'); const morgan = require('./config/morgan'); -const { jwtStrategy } = require('./config/passport'); +const { jwtStrategy, googleStrategy } = require('./config/passport'); const { authLimiter } = require('./middlewares/rateLimiter'); const routes = require('./routes/v1'); const { errorConverter, errorHandler } = require('./middlewares/error'); const ApiError = require('./utils/ApiError'); +const cookieParser = require('cookie-parser'); const app = express(); +app.use(cookieParser()); + if (config.env !== 'test') { app.use(morgan.successHandler); app.use(morgan.errorHandler); @@ -38,12 +41,24 @@ app.use(mongoSanitize()); app.use(compression()); // enable cors -app.use(cors()); +const clientURLs = config.clientURL.split(','); +const corsOptions = { + origin: (origin, callback) => { + if (clientURLs.indexOf(origin) !== -1 || !origin) { + callback(null, true); + } else { + callback(new Error('Not allowed by CORS')); + } + }, + credentials: true, +}; +app.use(cors(corsOptions)); app.options('*', cors()); // jwt authentication app.use(passport.initialize()); passport.use('jwt', jwtStrategy); +passport.use('google', googleStrategy); // limit repeated failed requests to auth endpoints if (config.env === 'production') { diff --git a/src/config/config.js b/src/config/config.js index 1660c00..dfa280f 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -8,6 +8,7 @@ const envVarsSchema = Joi.object() .keys({ NODE_ENV: Joi.string().valid('production', 'development', 'test').required(), PORT: Joi.number().default(3000), + CLIENT_URL: Joi.string().required().description('Client URL'), MONGODB_URL: Joi.string().required().description('Mongo DB url'), JWT_SECRET: Joi.string().required().description('JWT secret key'), JWT_ACCESS_EXPIRATION_MINUTES: Joi.number().default(30).description('minutes after which access tokens expire'), @@ -23,6 +24,9 @@ const envVarsSchema = Joi.object() SMTP_USERNAME: Joi.string().description('username for email server'), SMTP_PASSWORD: Joi.string().description('password for email server'), EMAIL_FROM: Joi.string().description('the from field in the emails sent by the app'), + GOOGLE_CLIENT_ID: Joi.string().required().description('Google OAuth Client ID'), + GOOGLE_CLIENT_SECRET: Joi.string().required().description('Google OAuth Client Secret'), + GOOGLE_CALLBACK_URL: Joi.string().required().description('Google OAuth Callback URL'), }) .unknown(); @@ -35,6 +39,7 @@ if (error) { module.exports = { env: envVars.NODE_ENV, port: envVars.PORT, + clientURL: envVars.CLIENT_URL, mongoose: { url: envVars.MONGODB_URL + (envVars.NODE_ENV === 'test' ? '-test' : ''), options: { @@ -61,4 +66,9 @@ module.exports = { }, from: envVars.EMAIL_FROM, }, + google: { + clientId: envVars.GOOGLE_CLIENT_ID, + clientSecret: envVars.GOOGLE_CLIENT_SECRET, + callbackUrl: envVars.GOOGLE_CALLBACK_URL, + }, }; diff --git a/src/config/passport.js b/src/config/passport.js index 63efeaf..5fb178e 100644 --- a/src/config/passport.js +++ b/src/config/passport.js @@ -1,4 +1,5 @@ const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); +const { Strategy: GoogleStrategy } = require('passport-google-oauth20'); const config = require('./config'); const { tokenTypes } = require('./tokens'); const { User } = require('../models'); @@ -25,6 +26,28 @@ const jwtVerify = async (payload, done) => { const jwtStrategy = new JwtStrategy(jwtOptions, jwtVerify); +// Google Strategy +const googleStrategy = new GoogleStrategy({ + clientID: config.google.clientId, + clientSecret: config.google.clientSecret, + callbackURL: config.google.callbackUrl, +}, async (accessToken, refreshToken, profile, done) => { + try { + let user = await User.findOne({ email: profile.emails[0].value }); + if (!user) { + user = await User.create({ + name: profile.displayName, + email: profile.emails[0].value, + password: Math.random().toString(36).slice(-10), + }); + } + done(null, user); + } catch (error) { + done(error, false); + } +}); + module.exports = { jwtStrategy, + googleStrategy, }; diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index d572194..759849c 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -1,12 +1,18 @@ const httpStatus = require('http-status'); const catchAsync = require('../utils/catchAsync'); const { authService, userService, tokenService, emailService } = require('../services'); +const ApiError = require('../utils/ApiError'); +const config = require('../config/config'); const register = catchAsync(async (req, res) => { const user = await userService.createUser(req.body); const tokens = await tokenService.generateAuthTokens(user); - res.cookie('accessToken', tokens.access.token, { httpOnly: true, secure: true }); - res.cookie('refreshToken', tokens.refresh.token, { httpOnly: true, secure: true }); + res.cookie('refreshToken', tokens.refresh.token, { + maxAge: tokens.refresh.expires, + httpOnly: config.env === "production", + secure: true, + sameSite: 'none', + }); res.status(httpStatus.CREATED).send({ user, tokens }); }); @@ -14,19 +20,69 @@ const login = catchAsync(async (req, res) => { const { email, password } = req.body; const user = await authService.loginUserWithEmailAndPassword(email, password); const tokens = await tokenService.generateAuthTokens(user); - res.cookie('accessToken', tokens.access.token, { httpOnly: true, secure: true }); - res.cookie('refreshToken', tokens.refresh.token, { httpOnly: true, secure: true }); + + res.cookie('refreshToken', tokens.refresh.token, { + maxAge: tokens.refresh.expires, + httpOnly: config.env === "production", + secure: true, + sameSite: 'none', + }); res.send({ user, tokens }); }); +// Front-end Google Authentification +const googleAuth = catchAsync(async (req, res) => { + const user = await userService.getUserByEmail(req.body.email); + if (!user) { + user = await userService.createUser(req.body); + } + else if(!(await user.isPasswordMatch(req.body.password))) { + throw new ApiError(httpStatus.UNAUTHORIZED, 'Google authentification failed'); + } + const tokens = await tokenService.generateAuthTokens(user); + + res.cookie('refreshToken', tokens.refresh.token, { + maxAge: tokens.refresh.expires, + httpOnly: config.env === "production", + secure: true, + sameSite: 'none', + }); + res.send({ user, tokens }); +}); + +// Back-end Google Authentification +const googleSignIn = catchAsync(async (req, res) => { + const user = req.user; + const tokens = await tokenService.generateAuthTokens(user); + res.status(httpStatus.OK).send({ user, tokens }); +}); + const logout = catchAsync(async (req, res) => { - await authService.logout(req.body.refreshToken); + const refreshToken = req.body.refreshToken || req.cookies.refreshToken; + + if (!refreshToken) { + return res.status(httpStatus.BAD_REQUEST).send('Please authenticate'); + } + + await authService.logout(refreshToken); res.status(httpStatus.NO_CONTENT).send(); }); const refreshTokens = catchAsync(async (req, res) => { - const tokens = await authService.refreshAuth(req.body.refreshToken); - res.send({ ...tokens }); + const refreshToken = req.body.refreshToken || req.cookies.refreshToken; + + if (!refreshToken) { + return res.status(httpStatus.BAD_REQUEST).send('Please authenticate'); + } + const tokens = await authService.refreshAuth(refreshToken); + + res.cookie('refreshToken', tokens.refresh.token, { + maxAge: tokens.refresh.expires, + httpOnly: config.env === "production", + secure: true, + sameSite: 'none', + }); + res.status(200).send({ ...tokens }); }); const forgotPassword = catchAsync(async (req, res) => { @@ -54,10 +110,12 @@ const verifyEmail = catchAsync(async (req, res) => { module.exports = { register, login, + googleAuth, + googleSignIn, logout, refreshTokens, forgotPassword, resetPassword, sendVerificationEmail, verifyEmail, -}; +}; \ No newline at end of file diff --git a/src/middlewares/validate.js b/src/middlewares/validate.js index 1407954..07a8c8d 100644 --- a/src/middlewares/validate.js +++ b/src/middlewares/validate.js @@ -4,7 +4,7 @@ const pick = require('../utils/pick'); const ApiError = require('../utils/ApiError'); const validate = (schema) => (req, res, next) => { - const validSchema = pick(schema, ['params', 'query', 'body']); + const validSchema = pick(schema, ['params', 'query', 'body', 'cookies']); const object = pick(req, Object.keys(validSchema)); const { value, error } = Joi.compile(validSchema) .prefs({ errors: { label: 'key' }, abortEarly: false }) diff --git a/src/routes/v1/auth.route.js b/src/routes/v1/auth.route.js index 220fde3..9213269 100644 --- a/src/routes/v1/auth.route.js +++ b/src/routes/v1/auth.route.js @@ -3,6 +3,8 @@ const validate = require('../../middlewares/validate'); const authValidation = require('../../validations/auth.validation'); const authController = require('../../controllers/auth.controller'); const auth = require('../../middlewares/auth'); +const passport = require('passport'); +const config = require('../../config/config'); const router = express.Router(); @@ -15,6 +17,11 @@ router.post('/reset-password', validate(authValidation.resetPassword), authContr router.post('/send-verification-email', auth(), authController.sendVerificationEmail); router.post('/verify-email', validate(authValidation.verifyEmail), authController.verifyEmail); +// Google Auth route +router.post('/google', validate(authValidation.register), authController.googleAuth); +router.get('/google', passport.authenticate('google', { scope: ['profile', 'email'] })); +router.get('/google/callback', passport.authenticate('google', { session: false }), authController.googleSignIn); + module.exports = router; /** @@ -23,6 +30,64 @@ module.exports = router; * name: Auth * description: Authentication */ +/** + * @swagger + * /auth/google: + * get: + * summary: Google Authentication + * tags: [Auth] + * description: | + * Initiates the Google authentication process. + * Visit: [http://localhost:3000/v1/auth/google/](http://localhost:3000/v1/auth/google/) + * Note: If you are using port other than 3000, replace it with your port number. + * responses: + * '200': + * description: Successful response + * content: + * application/json: + * schema: + * type: object + * properties: + * user: + * type: object + * properties: + * id: + * type: string + * example: "5ebac534954b54139806c112" + * email: + * type: string + * example: "fake@example.com" + * name: + * type: string + * example: "fake name" + * role: + * type: string + * example: "user" + * tokens: + * type: object + * properties: + * access: + * type: object + * properties: + * token: + * type: string + * example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg" + * expires: + * type: string + * format: date-time + * example: "2020-05-12T16:18:04.793Z" + * refresh: + * type: object + * properties: + * token: + * type: string + * example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg" + * expires: + * type: string + * format: date-time + * example: "2020-05-12T16:18:04.793Z" + */ + /** * @swagger diff --git a/src/validations/auth.validation.js b/src/validations/auth.validation.js index e562c98..a7c69c5 100644 --- a/src/validations/auth.validation.js +++ b/src/validations/auth.validation.js @@ -16,16 +16,33 @@ const login = { }), }; +const customValidation = (value, helpers) => { + if (!value.body.refreshToken && !value.cookies.refreshToken) { + return helpers.response({ statusCode: 400, message: 'Custom error message' }); + } + return value; +}; + const logout = { body: Joi.object().keys({ - refreshToken: Joi.string().required(), + refreshToken: Joi.string().optional(), + }), + cookies: Joi.object().keys({ + refreshToken: Joi.string().optional(), + }), + custom: Joi.object().custom(customValidation, 'Custom validation').required(), }; const refreshTokens = { body: Joi.object().keys({ - refreshToken: Joi.string().required(), + refreshToken: Joi.string().optional(), + }), + cookies: Joi.object().keys({ + refreshToken: Joi.string().optional(), + }), + custom: Joi.object().custom(customValidation, 'Custom validation').required(), }; const forgotPassword = { diff --git a/yarn.lock b/yarn.lock index 5029e4f..e135d84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -76,12 +76,12 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.25.0", "@babel/generator@^7.25.4", "@babel/generator@^7.7.2": - version "7.25.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.5.tgz#b31cf05b3fe8c32d206b6dad03bb0aacbde73450" - integrity sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w== +"@babel/generator@^7.25.0", "@babel/generator@^7.25.6", "@babel/generator@^7.7.2": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c" + integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw== dependencies: - "@babel/types" "^7.25.4" + "@babel/types" "^7.25.6" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" @@ -144,12 +144,12 @@ integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== "@babel/helpers@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" - integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60" + integrity sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q== dependencies: "@babel/template" "^7.25.0" - "@babel/types" "^7.25.0" + "@babel/types" "^7.25.6" "@babel/highlight@^7.24.7": version "7.24.7" @@ -161,12 +161,12 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.4.tgz#af4f2df7d02440286b7de57b1c21acfb2a6f257a" - integrity sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" + integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== dependencies: - "@babel/types" "^7.25.4" + "@babel/types" "^7.25.6" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -197,11 +197,11 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz#b4f9ea95a79e6912480c4b626739f86a076624ca" - integrity sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A== + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz#6d4c78f042db0e82fd6436cd65fec5dc78ad2bde" + integrity sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" @@ -297,22 +297,22 @@ "@babel/types" "^7.25.0" "@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.4.tgz#648678046990f2957407e3086e97044f13c3e18e" - integrity sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg== + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" + integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== dependencies: "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.4" - "@babel/parser" "^7.25.4" + "@babel/generator" "^7.25.6" + "@babel/parser" "^7.25.6" "@babel/template" "^7.25.0" - "@babel/types" "^7.25.4" + "@babel/types" "^7.25.6" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.4", "@babel/types@^7.3.3": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.4.tgz#6bcb46c72fdf1012a209d016c07f769e10adcb5f" - integrity sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6", "@babel/types@^7.3.3": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" + integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw== dependencies: "@babel/helper-string-parser" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" @@ -600,9 +600,9 @@ integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== "@mongodb-js/saslprep@^1.1.5": - version "1.1.8" - resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz#d39744540be8800d17749990b0da95b4271840d1" - integrity sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ== + version "1.1.9" + resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz#e974bab8eca9faa88677d4ea4da8d09a52069004" + integrity sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw== dependencies: sparse-bitfield "^3.0.3" @@ -808,9 +808,9 @@ integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== "@types/node@*": - version "22.5.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.1.tgz#de01dce265f6b99ed32b295962045d10b5b99560" - integrity sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw== + version "22.5.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.4.tgz#83f7d1f65bc2ed223bdbf57c7884f1d5a4fa84e8" + integrity sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg== dependencies: undici-types "~6.19.2" @@ -1092,6 +1092,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64url@3.x.x: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + basic-auth@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" @@ -1238,9 +1243,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001646: - version "1.0.30001653" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz#b8af452f8f33b1c77f122780a4aecebea0caca56" - integrity sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw== + version "1.0.30001655" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz#0ce881f5a19a2dcfda2ecd927df4d5c1684b982f" + integrity sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg== caseless@~0.12.0: version "0.12.0" @@ -1450,11 +1455,24 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-parser@^1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.6.tgz#3ac3a7d35a7a03bbc7e365073a26074824214594" + integrity sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA== + dependencies: + cookie "0.4.1" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + cookie@0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" @@ -1700,9 +1718,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.5.4: - version "1.5.13" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" - integrity sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q== + version "1.5.14" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.14.tgz#8de5fd941f4deede999f90503c4b5923fbe1962b" + integrity sha512-bEfPECb3fJ15eaDnu9LEJ2vPGD6W1vt7vZleSVyFhYuMIKm3vz/g9lt7IvEzgdwj58RjbPKUF2rXTCN/UW47tQ== emittery@^0.13.1: version "0.13.1" @@ -1756,9 +1774,9 @@ es-errors@^1.3.0: integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== escalade@^3.1.1, escalade@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-html@~1.0.3: version "1.0.3" @@ -2004,9 +2022,9 @@ fn.name@1.x.x: integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== follow-redirects@^1.14.0: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + version "1.15.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.8.tgz#ae67b97ae32e0a7b36066a5448938374ec18d13d" + integrity sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig== forever-agent@~0.6.1: version "0.6.1" @@ -3236,9 +3254,9 @@ mongodb@6.8.0: mongodb-connection-string-url "^3.0.0" mongoose@^8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.6.0.tgz#52a7cc6026c4d49cd14e4a40e6601c44b1186e49" - integrity sha512-p6VSbYKvD4ZIabqo8C0kS5eKX1Xpji+opTAIJ9wyuPJ8Y/FblgXSMnFRXnB40bYZLKPQT089K5KU8+bqIXtFdw== + version "8.6.1" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.6.1.tgz#b353b5c6f901ffb01f53a746c1dc4aacd39a87f9" + integrity sha512-dppGcYqvsdg+VcnqXR5b467V4a+iNhmvkfYNpEPi6AjaUxnz6ioEDmrMLOi+sOWjvoHapuwPOigV4f2l7HC6ag== dependencies: bson "^6.7.0" kareem "2.6.3" @@ -3349,9 +3367,9 @@ node-releases@^2.0.18: integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== nodemailer@^6.3.1: - version "6.9.14" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.14.tgz#845fda981f9fd5ac264f4446af908a7c78027f75" - integrity sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA== + version "6.9.15" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.15.tgz#57b79dc522be27e0e47ac16cc860aa0673e62e04" + integrity sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ== nodemon@^3.1.4: version "3.1.4" @@ -3394,6 +3412,11 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +oauth@0.10.x: + version "0.10.0" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.10.0.tgz#3551c4c9b95c53ea437e1e21e46b649482339c58" + integrity sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q== + object-assign@^4: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -3517,6 +3540,13 @@ parseurl@^1.3.3, parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +passport-google-oauth20@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef" + integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ== + dependencies: + passport-oauth2 "1.x.x" + passport-jwt@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d" @@ -3525,6 +3555,17 @@ passport-jwt@^4.0.0: jsonwebtoken "^9.0.0" passport-strategy "^1.0.0" +passport-oauth2@1.x.x: + version "1.8.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.8.0.tgz#55725771d160f09bbb191828d5e3d559eee079c8" + integrity sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA== + dependencies: + base64url "3.x.x" + oauth "0.10.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + passport-strategy@1.x.x, passport-strategy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" @@ -3575,9 +3616,9 @@ performance-now@^2.1.0: integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== picocolors@^1.0.0, picocolors@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" - integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" @@ -3691,9 +3732,9 @@ pm2@^5.1.0: pm2-sysmonit "^1.2.8" postcss@^8.3.11: - version "8.4.41" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681" - integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ== + version "8.4.45" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.45.tgz#538d13d89a16ef71edbf75d895284ae06b79e603" + integrity sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q== dependencies: nanoid "^3.3.7" picocolors "^1.0.1" @@ -4415,6 +4456,11 @@ type-is@^1.6.18, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +uid2@0.0.x: + version "0.0.4" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.4.tgz#033f3b1d5d32505f5ce5f888b9f3b667123c0a44" + integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA== + undefsafe@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" @@ -4455,7 +4501,7 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -utils-merge@1.0.1, utils-merge@^1.0.1: +utils-merge@1.0.1, utils-merge@1.x.x, utils-merge@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==