From dac19038a309f7562cf3168969385e33ea975262 Mon Sep 17 00:00:00 2001 From: Dan Orlando Date: Sun, 7 May 2023 10:04:51 -0700 Subject: [PATCH] feat: Auth and User System (#205) * server-side JWT auth implementation * move oauth routes and strategies, fix bugs * backend modifications for wiring up the frontend login and reg forms * Add frontend data services for login and registration * Add login and registration forms * Implment auth context, functional client side auth * protect routes with jwt auth * finish local strategy (using local storage) * Start setting up google auth * disable token refresh, remove old auth middleware * refactor client, add ApiErrorBoundary context * disable google and facebook strategies * fix: fix presets not displaying specific to user * fix: fix issue with browser refresh * fix: casing issue with User.js (#11) * delete user.js to be renamed * fix: fix casing issue with User.js * comment out api error watcher temporarily * fix: issue with api error watcher (#12) * delete user.js to be renamed * fix: fix casing issue with User.js * comment out api error watcher temporarily * feat: add google auth social login * fix: make google login url dynamic based on dev/prod * fix: bug where UI is briefly displayed before redirecting to login * fix: fix cookie expires value for local auth * Update README.md * Update LOCAL_INSTALL structure * Add local testing instructions * Only load google strategy if client id and secret are provided * Update .env.example files with new params * fix issue with not redirecting to register form * only show google login button if value is set in .env * cleanup log messages * Add label to button for google login on login form * doc: fix client/server url values in .env.example * feat: add error message details to registration failure * Restore preventing paste on confirm password * auto-login user after registering * feat: forgot password (#24) * make login/reg pages look like openai's * add password reset data services * new form designs similar to openai, add password reset pages * add api's for password reset * email utils for password reset * remove bcrypt salt rounds from process.env * refactor: restructure api auth code, consolidate routes (#25) * add api's for password reset * remove bcrypt salt rounds from process.env * refactor: consolidate auth routes, use controller pattern * refactor: code cleanup * feat: migrate data to first user (#26) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes after refactor (#27) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: issue with auto-login when logging out then logging in with new browser window (#28) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: fix issue with auto-login in new tab * doc: Update README and .env.example files with user system information (#29) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: fix issue with auto-login in new tab * doc: update README and .env.example files * Fixup: LOCAL_INSTALL.md PS instructions (#200) (#30) Co-authored-by: alfredo-f * feat: send user with completion to protect against abuse (#31) * Fixup: LOCAL_INSTALL.md PS instructions (#200) * server-side JWT auth implementation * move oauth routes and strategies, fix bugs * backend modifications for wiring up the frontend login and reg forms * Add frontend data services for login and registration * Add login and registration forms * Implment auth context, functional client side auth * protect routes with jwt auth * finish local strategy (using local storage) * Start setting up google auth * disable token refresh, remove old auth middleware * refactor client, add ApiErrorBoundary context * disable google and facebook strategies * fix: fix presets not displaying specific to user * fix: fix issue with browser refresh * fix: casing issue with User.js (#11) * delete user.js to be renamed * fix: fix casing issue with User.js * comment out api error watcher temporarily * feat: add google auth social login * fix: make google login url dynamic based on dev/prod * fix: bug where UI is briefly displayed before redirecting to login * fix: fix cookie expires value for local auth * Only load google strategy if client id and secret are provided * Update .env.example files with new params * fix issue with not redirecting to register form * only show google login button if value is set in .env * cleanup log messages * Add label to button for google login on login form * doc: fix client/server url values in .env.example * feat: add error message details to registration failure * Restore preventing paste on confirm password * auto-login user after registering * feat: forgot password (#24) * make login/reg pages look like openai's * add password reset data services * new form designs similar to openai, add password reset pages * add api's for password reset * email utils for password reset * remove bcrypt salt rounds from process.env * refactor: restructure api auth code, consolidate routes (#25) * add api's for password reset * remove bcrypt salt rounds from process.env * refactor: consolidate auth routes, use controller pattern * refactor: code cleanup * feat: migrate data to first user (#26) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes after refactor (#27) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: issue with auto-login when logging out then logging in with new browser window (#28) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: fix issue with auto-login in new tab * doc: Update README and .env.example files with user system information (#29) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: fix issue with auto-login in new tab * doc: update README and .env.example files * Send user id to openai to protect against abuse * add meilisearch to gitignore * Remove webpack --------- Co-authored-by: alfredo-f --------- Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com> Co-authored-by: Alfredo Fomitchenko --- .gitignore | 7 +- Dockerfile | 2 +- Dockerfile-app | 2 +- README.md | 51 +- api/.env.example | 48 +- api/app/clients/chatgpt-browser.js | 8 +- api/app/clients/chatgpt-client.js | 6 +- api/middleware/requireJwtAuth.js | 5 + api/middleware/requireLocalAuth.js | 31 + api/models/Conversation.js | 2 +- api/models/User.js | 177 + api/models/schema/tokenSchema.js | 22 + api/package-lock.json | 1199 ++++++- api/package.json | 15 +- api/server/controllers/auth.controller.js | 180 + ...errorController.js => error.controller.js} | 0 api/server/index.js | 60 +- api/server/routes/ask/addToCache.js | 1 - api/server/routes/ask/askBingAI.js | 13 +- api/server/routes/ask/askChatGPTBrowser.js | 17 +- api/server/routes/ask/askOpenAI.js | 24 +- api/server/routes/auth.js | 74 +- api/server/routes/authYourLogin.js | 44 - api/server/routes/convos.js | 21 +- api/server/routes/index.js | 8 +- api/server/routes/me.js | 16 - api/server/routes/messages.js | 3 +- api/server/routes/oauth.js | 64 + api/server/routes/presets.js | 17 +- api/server/routes/search.js | 6 +- api/server/routes/tokenizer.js | 3 +- api/server/services/auth.service.js | 197 ++ api/strategies/facebookStrategy.js | 60 + api/strategies/googleStrategy.js | 44 + api/strategies/jwtStrategy.js | 29 + api/strategies/localStrategy.js | 68 + api/strategies/validators.js | 24 + api/utils/debug.js | 46 + api/utils/emails/passwordReset.handlebars | 11 + .../emails/requestPasswordReset.handlebars | 13 + api/utils/migrateDataToFirstUser.js | 30 + api/utils/sendEmail.js | 54 + client/.env.example | 16 + client/package-lock.json | 3053 ++--------------- client/package.json | 15 +- client/src/App.jsx | 143 +- .../src/components/Auth/ApiErrorWatcher.tsx | 18 + client/src/components/Auth/Login.tsx | 184 + client/src/components/Auth/Registration.tsx | 315 ++ .../components/Auth/RequestPasswordReset.tsx | 115 + client/src/components/Auth/ResetPassword.tsx | 176 + client/src/components/Auth/index.ts | 4 + .../src/components/MessageHandler/index.jsx | 7 +- client/src/components/Nav/Logout.jsx | 14 +- client/src/components/Nav/index.jsx | 5 +- client/src/data-provider/api-endpoints.ts | 34 +- client/src/data-provider/data-service.ts | 28 + .../src/data-provider/react-query-service.ts | 64 +- client/src/data-provider/request.ts | 2 +- client/src/data-provider/types.ts | 37 +- client/src/hooks/ApiErrorBoundaryContext.tsx | 33 + client/src/hooks/AuthContext.tsx | 175 + client/src/main.jsx | 15 +- client/src/routes/Root.jsx | 41 +- client/src/utils/userAuth.js | 23 - client/tailwind.config.cjs | 14 +- client/vite.config.ts | 18 +- client/webpack.config.js | 111 - 68 files changed, 3968 insertions(+), 3394 deletions(-) create mode 100644 api/middleware/requireJwtAuth.js create mode 100644 api/middleware/requireLocalAuth.js create mode 100644 api/models/User.js create mode 100644 api/models/schema/tokenSchema.js create mode 100644 api/server/controllers/auth.controller.js rename api/server/controllers/{errorController.js => error.controller.js} (100%) delete mode 100644 api/server/routes/authYourLogin.js delete mode 100644 api/server/routes/me.js create mode 100644 api/server/routes/oauth.js create mode 100644 api/server/services/auth.service.js create mode 100644 api/strategies/facebookStrategy.js create mode 100644 api/strategies/googleStrategy.js create mode 100644 api/strategies/jwtStrategy.js create mode 100644 api/strategies/localStrategy.js create mode 100644 api/strategies/validators.js create mode 100644 api/utils/debug.js create mode 100644 api/utils/emails/passwordReset.handlebars create mode 100644 api/utils/emails/requestPasswordReset.handlebars create mode 100644 api/utils/migrateDataToFirstUser.js create mode 100644 api/utils/sendEmail.js create mode 100644 client/.env.example create mode 100644 client/src/components/Auth/ApiErrorWatcher.tsx create mode 100644 client/src/components/Auth/Login.tsx create mode 100644 client/src/components/Auth/Registration.tsx create mode 100644 client/src/components/Auth/RequestPasswordReset.tsx create mode 100644 client/src/components/Auth/ResetPassword.tsx create mode 100644 client/src/components/Auth/index.ts create mode 100644 client/src/hooks/ApiErrorBoundaryContext.tsx create mode 100644 client/src/hooks/AuthContext.tsx delete mode 100644 client/src/utils/userAuth.js delete mode 100644 client/webpack.config.js diff --git a/.gitignore b/.gitignore index be695425e..211d91687 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,9 @@ src/style - official.css /e2e/specs/.test-results/ /e2e/playwright-report/ /playwright/.cache/ -.DS_Store \ No newline at end of file +.DS_Store + +# meilisearch +meilisearch +data.ms/* + diff --git a/Dockerfile b/Dockerfile index 118bfee8f..a2b7fe786 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN npm ci COPY /client/ /client/ # Set the memory limit for Node.js ENV NODE_OPTIONS="--max-old-space-size=2048" -# Build webpack artifacts +# Build artifacts RUN npm run build FROM node:19-alpine AS node-api diff --git a/Dockerfile-app b/Dockerfile-app index 3a71564a1..53c1759cf 100644 --- a/Dockerfile-app +++ b/Dockerfile-app @@ -18,7 +18,7 @@ COPY /api/ /app/api/ # Set the memory limit for Node.js ENV NODE_OPTIONS="--max-old-space-size=2048" -# Build webpack artifacts for the client +# Build artifacts for the client RUN cd /app/client && npm run build # Create the necessary directory and copy the client side code to the api directory diff --git a/README.md b/README.md index f788b6634..56aa4b329 100644 --- a/README.md +++ b/README.md @@ -149,10 +149,6 @@ Currently, this project is only functional with the `text-davinci-003` model. # Table of Contents -- [ChatGPT Clone](#chatgpt-clone) - - [All AI Conversations under One Roof.](#all-ai-conversations-under-one-roof) - - [Features](#features) - - [Updates](#updates) - [Table of Contents](#table-of-contents) - [Roadmap](#roadmap) - [Tech Stack](#tech-stack) @@ -160,10 +156,12 @@ Currently, this project is only functional with the `text-davinci-003` model. - [Prerequisites](#prerequisites) - [Usage](#usage) - [Local](#local) + - [**Automated Installer (Windows)**](#automated-installer-windows) + - [**In-Depth Instructions**](#in-depth-instructions) - [Docker](#docker) - [Access Tokens](#access-tokens) - [Proxy](#proxy) - - [User System](#user-system) + - [User/Auth System](#userauth-system) - [Updating](#updating) - [Use Cases](#use-cases) - [Origin](#origin) @@ -310,45 +308,30 @@ set in docker-compose.yml file, under services - api - environment -### User System +### User/Auth System -By default, there is no user system enabled, so anyone can access your server. +**First Time Setup** +([danorlando](https://github.com/danorlando)) The first time you run the application, you should register a new account by clicking the "Sign up" link on the login page. The first account registered will be recieve an admin role. The admin account does not currently have extended functionality, but is valuable should you choose to create an admin dashboard for user management. -**This project is not designed to provide a complete and full-featured user system.** It's not high priority task and might never be provided. +**Migrating Previous Conversations and Presets to new User Account** +When the first account is registered, the application will automatically migrate any conversations and presets that you created before the user system was implemented to that account. -[wtlyu](https://github.com/wtlyu) provide a sample user system structure, that you can implement your own user system. It's simple and not a ready-for-use edition. +IMPORTANT: if you use login for the first time with a social login account (eg. Google, facebook, etc.), the conversations and presets that you created before the user system was implemented will NOT be migrated to that account. You should register and login with a local account (email and password) for the first time. -(If you want to implement your user system, open this ↓) +**OAuth2/Social Login** +The application is setup to support OAuth2/Social Login with Google. All of the code is in place for Facebook login as well, but this has not been tested because the setup process with Facebook was honestly just too painful for me to deal with. I plan to add support for other OAuth2 providers including Github and Discord at a later time. -
-Implement your own user system +To enable Google login, you must create an application in the [Google Cloud Console](https://cloud.google.com) and provide the client ID and client secret in the [/api/.env](https://github.com/danny-avila/chatgpt-clone/blob/main/api/.env.example) file, then set `VITE_SHOW_GOOGLE_LOGIN_OPTION=true` in the [/client/.env](https://github.com/danny-avila/chatgpt-clone/blob/main/client/.env.example) file. -To enable the user system, set `ENABLE_USER_SYSTEM=1` in your `.env` file. - -The sample structure is simple. It provide three basic endpoint: - -1. `/auth/login` will redirect to your own login url. In the sample code, it's `/auth/your_login_page`. -2. `/auth/logout` will redirect to your own logout url. In the sample code, it's `/auth/your_login_page/logout`. -3. `/api/me` will return the userinfo: `{ username, display }`. - 1. `username` will be used in db, used to distinguish between users. - 2. `display` will be displayed in UI. - -The only one thing that drive user system work is `req.session.user`. Once it's set, the client will be trusted. Set to `null` if logout. - -Please refer to `/api/server/routes/authYourLogin.js` file. It's very clear and simple to tell you how to implement your user system. - -Or you can ask chatGPT to write the code for you, here is one example to connect LDAP: - -``` -Please write me an express module, that serve the login and logout endpoint as a router. The login and logout uri is '/' and '/logout'. Once loginned, save display name and username in session.user, as {display, username}. Then redirect to '/'. Please write the code using express and other lib, and storage any server configuration in a config variable. I want the user to be connected to my LDAP server. -``` - -
+**Email and Password Reset** +Most of the code is in place for sending password reset emails, but is not yet feature-complete as I have not setup an email server to test it. Currently, submitting a password reset request will then display a link with the one-time reset token that can then be used to reset the password. Understanding that this is a considerable security hazard, email integration will be included in the next release. +***Warning*** +If you previously implemented your own user system using the original scaffolding that was provided, you will no longer see conversations and presets by switching to the new user system. This is because of a design flaw in the scaffolding implementation that was problematic for the inclusion of social login. ### Updating -- As the project is still a work-in-progress, you should pull the latest and run the steps over. Reset your browser cache/clear site data. +- As the project is still a work-in-progress, you should pull the latest and run the steps over. Reset your browser cache/clear cookies and site data. ## Use Cases ## diff --git a/api/.env.example b/api/.env.example index b80ad3b60..0b1922743 100644 --- a/api/.env.example +++ b/api/.env.example @@ -1,5 +1,5 @@ ########################## -# Server configuration. +# Server configuration: ########################## # The server will listen to localhost:3080 by default. You can change the target IP as you want. @@ -7,15 +7,16 @@ # or expose this from a Docker container, set host to 0.0.0.0 or your external IP interface. # Tips: Setting host to 0.0.0.0 means listening on all interfaces. It's not a real IP. # Use localhost:port rather than 0.0.0.0:port to access the server. +# Set Node env to development if running in dev mode. HOST=localhost PORT=3080 -NODE_ENV=development +NODE_ENV=production # Change this to proxy any API request. # It's useful if your machine has difficulty calling the original API server. # PROXY= -# Change this to your MongoDB URI if different and I recommend appending chatgpt-clone +# Change this to your MongoDB URI if different. I recommend appending chatgpt-clone. MONGO_URI=mongodb://127.0.0.1:27017/chatgpt-clone ########################## @@ -44,7 +45,7 @@ OPENAI_MODELS=gpt-3.5-turbo,gpt-3.5-turbo-0301,text-davinci-003,gpt-4 # BingAI Tokens: the "_U" cookies value from bing.com # Set to "user_provided" to allow the user to provide its token from the UI. # Leave it blank to disable this endpoint. -BINGAI_TOKEN=user_provided +BINGAI_TOKEN="user_provided" # BingAI Host: # Necessary for some people in different countries, e.g. China (https://cn.bing.com) @@ -60,7 +61,7 @@ BINGAI_TOKEN=user_provided # Exposes your access token to `CHATGPT_REVERSE_PROXY` # Set to "user_provided" to allow the user to provide its token from the UI. # Leave it blank to disable this endpoint -CHATGPT_TOKEN=user_provided +CHATGPT_TOKEN="user_provided" # Identify the available models, separated by commas. The first will be default. # Leave it blank to use internal settings. @@ -78,7 +79,7 @@ CHATGPT_MODELS=text-davinci-002-render-sha,text-davinci-002-render-paid,gpt-4 # ENABLING SEARCH MESSAGES/CONVOS # Requires the installation of the free self-hosted Meilisearch or a paid Remote Plan (Remote not tested) # The easiest setup for this is through docker-compose, which takes care of it for you. -SEARCH=TRUE +SEARCH=false # REQUIRED FOR SEARCH: MeiliSearch Host, mainly for the API server to connect to the search server. # Replace '0.0.0.0' with 'meilisearch' if serving MeiliSearch with docker-compose. @@ -94,14 +95,35 @@ MEILI_HTTP_ADDR=0.0.0.0:7700 # or if it is under 16 bytes. MeiliSearch will suggest a secure autogenerated master key. # Using docker, it seems recognized as production so use a secure key. # This is a ready made secure key for docker-compose, you can replace it with your own. -MEILI_MASTER_KEY=JKMW-hGc7v_D1FkJVdbRSDNFLZcUv3S75yrxXP0SmcU +MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt ########################## -# User System +# User System: ########################## -# ENABLING THE USER SYSTEM -# This is not a ready to use user system. -# Don't use it, unless you can write your own code. -# Do not uncomment this unless you implemented your own user system -# ENABLE_USER_SYSTEM= +# Google: +# Add your Google Client ID and Secret here, you must register an app with Google Cloud to get these values +# https://cloud.google.com/ +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +GOOGLE_CALLBACK_URL=/oauth/google/callback + +#JWT: +JWT_SECRET_DEV=secret + +# Add a secure secret for production if deploying to live domain. +JWT_SECRET_PROD=secret + +# Set the expiration delay for the secure cookie with the JWT token +# Delay is in millisecond e.g. 7 days is 1000*60*60*24*7 +SESSION_EXPIRY=1000 * 60 * 60 * 24 * 7 + +# Site URLs: +# Don't forget to set Node env to development in the Server configuration section above +# if you want to run in dev mode +CLIENT_URL_DEV=http://localhost:3090 +SERVER_URL_DEV=http://localhost:3080 + +# Change these values to domain if deploying: +CLIENT_URL_PROD=http://localhost:3080 +SERVER_URL_PROD=http://localhost:3080 diff --git a/api/app/clients/chatgpt-browser.js b/api/app/clients/chatgpt-browser.js index 529cb7eaa..540086dcd 100644 --- a/api/app/clients/chatgpt-browser.js +++ b/api/app/clients/chatgpt-browser.js @@ -8,7 +8,8 @@ const browserClient = async ({ model, token, onProgress, - abortController + abortController, + userId }) => { const { ChatGPTBrowserClient } = await import('@waylaidwanderer/chatgpt-api'); const store = { @@ -21,8 +22,9 @@ const browserClient = async ({ // Access token from https://chat.openai.com/api/auth/session accessToken: process.env.CHATGPT_TOKEN == 'user_provided' ? token : process.env.CHATGPT_TOKEN ?? null, model: model, - // debug: true - proxy: process.env.PROXY || null + debug: false, + proxy: process.env.PROXY || null, + user: userId }; const client = new ChatGPTBrowserClient(clientOptions, store); diff --git a/api/app/clients/chatgpt-client.js b/api/app/clients/chatgpt-client.js index 5a591c62a..2f56704ad 100644 --- a/api/app/clients/chatgpt-client.js +++ b/api/app/clients/chatgpt-client.js @@ -14,7 +14,8 @@ const askClient = async ({ presence_penalty, frequency_penalty, onProgress, - abortController + abortController, + userId }) => { const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default; const store = { @@ -36,7 +37,8 @@ const askClient = async ({ chatGptLabel, promptPrefix, proxy: process.env.PROXY || null, - debug: false + debug: false, + user: userId }; const client = new ChatGPTClient(process.env.OPENAI_KEY, clientOptions, store); diff --git a/api/middleware/requireJwtAuth.js b/api/middleware/requireJwtAuth.js new file mode 100644 index 000000000..5c9a51f92 --- /dev/null +++ b/api/middleware/requireJwtAuth.js @@ -0,0 +1,5 @@ +const passport = require('passport'); + +const requireJwtAuth = passport.authenticate('jwt', { session: false }); + +module.exports = requireJwtAuth; diff --git a/api/middleware/requireLocalAuth.js b/api/middleware/requireLocalAuth.js new file mode 100644 index 000000000..84b3a85f0 --- /dev/null +++ b/api/middleware/requireLocalAuth.js @@ -0,0 +1,31 @@ +const passport = require('passport'); +const DebugControl = require('../utils/debug.js'); + +function log({ title, parameters }) { + DebugControl.log.functionName(title); + if (parameters) { + DebugControl.log.parameters(parameters); + } +} + +const requireLocalAuth = (req, res, next) => { + passport.authenticate('local', (err, user, info) => { + if (err) { + log({ + title: '(requireLocalAuth) Error at passport.authenticate', + parameters: [{ name: 'error', value: err }] + }); + return next(err); + } + if (!user) { + log({ + title: '(requireLocalAuth) Error: No user', + }); + return res.status(422).send(info); + } + req.user = user; + next(); + })(req, res, next); +}; + +module.exports = requireLocalAuth; diff --git a/api/models/Conversation.js b/api/models/Conversation.js index 580a07f6e..2333ed672 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -3,6 +3,7 @@ const Conversation = require('./schema/convoSchema'); const { getMessages, deleteMessages } = require('./Message'); const getConvo = async (user, conversationId) => { + console.log('getConvo -> userId', user); try { return await Conversation.findOne({ user, conversationId }).exec(); } catch (error) { @@ -39,7 +40,6 @@ module.exports = { .skip((pageNumber - 1) * pageSize) .limit(pageSize) .exec(); - return { conversations: convos, pages: totalPages, pageNumber, pageSize }; } catch (error) { console.log(error); diff --git a/api/models/User.js b/api/models/User.js new file mode 100644 index 000000000..9333e507f --- /dev/null +++ b/api/models/User.js @@ -0,0 +1,177 @@ +const mongoose = require('mongoose'); +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const Joi = require('joi'); +const DebugControl = require('../utils/debug.js'); + +function log({ title, parameters }) { + DebugControl.log.functionName(title); + DebugControl.log.parameters(parameters); +} + +const Session = mongoose.Schema({ + refreshToken: { + type: String, + default: '' + } +}); + +const userSchema = mongoose.Schema( + { + name: { + type: String + }, + username: { + type: String, + lowercase: true, + required: [true, "can't be blank"], + match: [/^[a-zA-Z0-9_]+$/, 'is invalid'], + index: true + }, + email: { + type: String, + required: [true, "can't be blank"], + lowercase: true, + unique: true, + match: [/\S+@\S+\.\S+/, 'is invalid'], + index: true + }, + emailVerified: { + type: Boolean, + required: true, + default: false + }, + password: { + type: String, + trim: true, + minlength: 8, + maxlength: 60 + }, + avatar: { + type: String, + required: false + }, + provider: { + type: String, + required: true, + default: 'local' + }, + role: { + type: String, + default: 'USER' + }, + googleId: { + type: String, + unique: true, + sparse: true + }, + facebookId: { + type: String, + unique: true, + sparse: true + }, + refreshToken: { + type: [Session] + } + }, + { timestamps: true } +); + +//Remove refreshToken from the response +userSchema.set('toJSON', { + transform: function (doc, ret, options) { + delete ret.refreshToken; + return ret; + } +}); + +userSchema.methods.toJSON = function () { + return { + id: this._id, + provider: this.provider, + email: this.email, + name: this.name, + username: this.username, + avatar: this.avatar, + role: this.role, + emailVerified: this.emailVerified, + createdAt: this.createdAt, + updatedAt: this.updatedAt + }; +}; + +const isProduction = process.env.NODE_ENV === 'production'; +const secretOrKey = isProduction ? process.env.JWT_SECRET_PROD : process.env.JWT_SECRET_DEV; +const refreshSecret = isProduction + ? process.env.REFRESH_TOKEN_SECRET_PROD + : process.env.REFRESH_TOKEN_SECRET_DEV; + +userSchema.methods.generateToken = function () { + const token = jwt.sign( + { + id: this._id, + username: this.username, + provider: this.provider, + email: this.email + }, + secretOrKey, + { expiresIn: eval(process.env.SESSION_EXPIRY) } + ); + return token; +}; + +userSchema.methods.generateRefreshToken = function () { + const refreshToken = jwt.sign( + { + id: this._id, + username: this.username, + provider: this.provider, + email: this.email + }, + refreshSecret, + { expiresIn: eval(process.env.REFRESH_TOKEN_EXPIRY) } + ); + return refreshToken; +}; + +userSchema.methods.comparePassword = function (candidatePassword, callback) { + bcrypt.compare(candidatePassword, this.password, (err, isMatch) => { + if (err) return callback(err); + callback(null, isMatch); + }); +}; + +module.exports.hashPassword = async (password) => { + + const hashedPassword = await new Promise((resolve, reject) => { + bcrypt.hash(password, 10, function (err, hash) { + if (err) reject(err); + else resolve(hash); + }); + }); + + return hashedPassword; +}; + +module.exports.validateUser = (user) => { + log({ + title: 'Validate User', + parameters: [{ name: 'Validate User', value: user }] + }); + const schema = { + avatar: Joi.any(), + name: Joi.string().min(2).max(80).required(), + username: Joi.string() + .min(2) + .max(80) + .regex(/^[a-zA-Z0-9_]+$/) + .required(), + password: Joi.string().min(8).max(60).allow('').allow(null) + }; + + return Joi.validate(user, schema); +}; + +const User = mongoose.model('User', userSchema); + +module.exports = User; diff --git a/api/models/schema/tokenSchema.js b/api/models/schema/tokenSchema.js new file mode 100644 index 000000000..2142348e3 --- /dev/null +++ b/api/models/schema/tokenSchema.js @@ -0,0 +1,22 @@ +const mongoose = require("mongoose"); +const Schema = mongoose.Schema; + +const tokenSchema = new Schema({ + userId: { + type: Schema.Types.ObjectId, + required: true, + ref: "user", + }, + token: { + type: String, + required: true, + }, + createdAt: { + type: Date, + required: true, + default: Date.now, + expires: 900, + }, +}); + +module.exports = mongoose.model("Token", tokenSchema); \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index 80765d1ff..719d89742 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -13,19 +13,32 @@ "@keyv/mongo": "^2.1.8", "@waylaidwanderer/chatgpt-api": "^1.35.0", "axios": "^1.3.4", + "bcrypt": "^5.1.0", + "bcryptjs": "^2.4.3", + "cookie": "^0.5.0", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "crypto": "^1.0.1", "dotenv": "^16.0.3", "eslint": "^8.36.0", "express": "^4.18.2", - "express-session": "^1.17.3", + "handlebars": "^4.7.7", "html": "^1.0.0", + "joi": "^14.3.1", + "jsonwebtoken": "^9.0.0", "keyv": "^4.5.2", "keyv-file": "^0.2.0", "lodash": "^4.17.21", "meilisearch": "^0.31.1", "mongoose": "^6.9.0", + "nodemailer": "^6.9.1", "openai": "^3.1.0", + "passport": "^0.6.0", + "passport-facebook": "^3.0.0", + "passport-github": "^1.1.0", + "passport-google-oauth20": "^2.0.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "sanitize": "^2.1.2" }, "devDependencies": { @@ -1567,6 +1580,39 @@ "pify": "^5.0.0" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1670,8 +1716,7 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/abort-controller": { "version": "3.0.0", @@ -1865,6 +1910,11 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -1889,6 +1939,31 @@ "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==" }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1956,6 +2031,32 @@ } ] }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2129,6 +2230,11 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2239,6 +2345,14 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -2323,6 +2437,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2353,6 +2475,11 @@ "typedarray": "^0.0.6" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2380,6 +2507,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", @@ -2469,6 +2616,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2486,6 +2638,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2510,6 +2670,14 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2812,45 +2980,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express-session": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", - "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", - "dependencies": { - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express-session/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express-session/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3209,6 +3338,28 @@ "universalify": "^0.1.0" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3233,6 +3384,43 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -3327,6 +3515,42 @@ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -3358,6 +3582,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", + "deprecated": "This module has moved and is now available at @hapi/hoek. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues." + }, "node_modules/html": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/html/-/html-1.0.0.tgz", @@ -3685,6 +3920,17 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "dependencies": { + "punycode": "2.x.x" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3724,6 +3970,17 @@ "node": ">= 6" } }, + "node_modules/joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "deprecated": "This module has moved and is now available at @hapi/joi. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.", + "dependencies": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, "node_modules/js-sdsl": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", @@ -3767,6 +4024,40 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", @@ -3865,6 +4156,28 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/media-typer": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3954,6 +4267,48 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mnemonist": { "version": "0.39.5", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.5.tgz", @@ -4059,6 +4414,16 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -4097,6 +4462,14 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/nodemailer": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz", + "integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "2.0.22", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", @@ -4178,6 +4551,22 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4215,14 +4604,6 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4397,6 +4778,103 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-facebook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/passport-facebook/-/passport-facebook-3.0.0.tgz", + "integrity": "sha512-K/qNzuFsFISYAyC1Nma4qgY/12V3RSLFdFVsPKXiKZt434wOvthFW1p7zKa1iQihQMRhaWorVE1o3Vi1o+ZgeQ==", + "dependencies": { + "passport-oauth2": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-github": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/passport-github/-/passport-github-1.1.0.tgz", + "integrity": "sha512-XARXJycE6fFh/dxF+Uut8OjlwbFEXgbPVj/+V+K7cvriRK7VcAOm+NgBmbiLM9Qv3SSxEAV+V6fIk89nYHXa8A==", + "dependencies": { + "passport-oauth2": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-google-oauth20": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", + "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "dependencies": { + "passport-oauth2": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-oauth2": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.7.0.tgz", + "integrity": "sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.9.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path": { "version": "0.12.7", "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", @@ -4436,6 +4914,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -4608,14 +5091,6 @@ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4922,6 +5397,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", @@ -5174,6 +5654,22 @@ "node": ">=4" } }, + "node_modules/tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5231,6 +5727,15 @@ "node": ">=0.6" } }, + "node_modules/topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "deprecated": "This module has moved and is now available at @hapi/topo. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.", + "dependencies": { + "hoek": "6.x.x" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -5298,17 +5803,23 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "dependencies": { - "random-bytes": "~1.0.0" + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" }, "engines": { - "node": ">= 0.8" + "node": ">=0.8.0" } }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -5445,6 +5956,32 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/widest-line": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", @@ -5467,6 +6004,11 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -7003,6 +7545,32 @@ "pify": "^5.0.0" } }, + "@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "dependencies": { + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7087,8 +7655,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "abort-controller": { "version": "3.0.0", @@ -7228,6 +7795,11 @@ "picomatch": "^2.0.4" } }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, "arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -7238,6 +7810,27 @@ "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==" }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -7288,6 +7881,25 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, + "bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + } + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -7417,6 +8029,11 @@ "ieee754": "^1.2.1" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -7491,6 +8108,11 @@ } } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, "cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -7542,6 +8164,11 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -7566,6 +8193,11 @@ "typedarray": "^0.0.6" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -7584,6 +8216,22 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, + "cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + } + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -7652,6 +8300,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7662,6 +8315,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -7680,6 +8338,14 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -7918,41 +8584,6 @@ } } }, - "express-session": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", - "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", - "requires": { - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "dependencies": { - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -8224,6 +8855,24 @@ "universalify": "^0.1.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8241,6 +8890,39 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, "get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -8307,6 +8989,30 @@ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -8326,6 +9032,16 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" + }, "html": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/html/-/html-1.0.0.tgz", @@ -8542,6 +9258,14 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + } + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -8580,6 +9304,16 @@ } } }, + "joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "requires": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, "js-sdsl": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", @@ -8616,6 +9350,36 @@ "graceful-fs": "^4.1.6" } }, + "jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kareem": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", @@ -8693,6 +9457,21 @@ "yallist": "^4.0.0" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "media-typer": { "version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" @@ -8757,6 +9536,35 @@ "brace-expansion": "^1.1.7" } }, + "minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==" + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, "mnemonist": { "version": "0.39.5", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.5.tgz", @@ -8840,6 +9648,16 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -8869,6 +9687,11 @@ } } }, + "nodemailer": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz", + "integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==" + }, "nodemon": { "version": "2.0.22", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", @@ -8927,6 +9750,22 @@ "path-key": "^3.0.0" } }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8955,11 +9794,6 @@ "ee-first": "1.1.1" } }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -9087,6 +9921,74 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "passport": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + } + }, + "passport-facebook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/passport-facebook/-/passport-facebook-3.0.0.tgz", + "integrity": "sha512-K/qNzuFsFISYAyC1Nma4qgY/12V3RSLFdFVsPKXiKZt434wOvthFW1p7zKa1iQihQMRhaWorVE1o3Vi1o+ZgeQ==", + "requires": { + "passport-oauth2": "1.x.x" + } + }, + "passport-github": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/passport-github/-/passport-github-1.1.0.tgz", + "integrity": "sha512-XARXJycE6fFh/dxF+Uut8OjlwbFEXgbPVj/+V+K7cvriRK7VcAOm+NgBmbiLM9Qv3SSxEAV+V6fIk89nYHXa8A==", + "requires": { + "passport-oauth2": "1.x.x" + } + }, + "passport-google-oauth20": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", + "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "requires": { + "passport-oauth2": "1.x.x" + } + }, + "passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "requires": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "requires": { + "passport-strategy": "1.x.x" + } + }, + "passport-oauth2": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.7.0.tgz", + "integrity": "sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==", + "requires": { + "base64url": "3.x.x", + "oauth": "0.9.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==" + }, "path": { "version": "0.12.7", "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", @@ -9117,6 +10019,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -9241,11 +10148,6 @@ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" - }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -9474,6 +10376,11 @@ "send": "0.18.0" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", @@ -9661,6 +10568,19 @@ "has-flag": "^3.0.0" } }, + "tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -9706,6 +10626,14 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "requires": { + "hoek": "6.x.x" + } + }, "touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -9755,13 +10683,16 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "requires": { - "random-bytes": "~1.0.0" - } + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true + }, + "uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" }, "undefsafe": { "version": "2.0.5", @@ -9868,6 +10799,31 @@ "isexe": "^2.0.0" } }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, "widest-line": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", @@ -9881,6 +10837,11 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, "wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", diff --git a/api/package.json b/api/package.json index 73fc7c16f..fc54156dc 100644 --- a/api/package.json +++ b/api/package.json @@ -23,19 +23,32 @@ "@keyv/mongo": "^2.1.8", "@waylaidwanderer/chatgpt-api": "^1.35.0", "axios": "^1.3.4", + "bcrypt": "^5.1.0", + "bcryptjs": "^2.4.3", + "cookie": "^0.5.0", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "crypto": "^1.0.1", "dotenv": "^16.0.3", "eslint": "^8.36.0", "express": "^4.18.2", - "express-session": "^1.17.3", + "handlebars": "^4.7.7", "html": "^1.0.0", + "joi": "^14.3.1", + "jsonwebtoken": "^9.0.0", "keyv": "^4.5.2", "keyv-file": "^0.2.0", "lodash": "^4.17.21", "meilisearch": "^0.31.1", "mongoose": "^6.9.0", + "nodemailer": "^6.9.1", "openai": "^3.1.0", + "passport": "^0.6.0", + "passport-facebook": "^3.0.0", + "passport-github": "^1.1.0", + "passport-google-oauth20": "^2.0.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "sanitize": "^2.1.2" }, "devDependencies": { diff --git a/api/server/controllers/auth.controller.js b/api/server/controllers/auth.controller.js new file mode 100644 index 000000000..eef3d5276 --- /dev/null +++ b/api/server/controllers/auth.controller.js @@ -0,0 +1,180 @@ +const { + loginUser, + logoutUser, + registerUser, + requestPasswordReset, + resetPassword, +} = require("../services/auth.service"); + +const isProduction = process.env.NODE_ENV === 'production'; + +const loginController = async (req, res) => { + try { + const token = req.user.generateToken(); + const user = await loginUser(req.user) + if(user) { + res.cookie('token', token, { + expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), + httpOnly: false, + secure: isProduction + }); + res.status(200).send({ token, user }); + } + else { + return res.status(400).json({ message: 'Invalid credentials' }); + } + } + catch (err) { + console.log(err); + return res.status(500).json({ message: err.message }); + } +}; + +const logoutController = async (req, res) => { + const { signedCookies = {} } = req; + const { refreshToken } = signedCookies; + try { + const logout = await logoutUser(req.user, refreshToken); + console.log(logout) + const { status, message } = logout; + if (status === 200) { + res.clearCookie('token'); + res.clearCookie('refreshToken'); + res.status(status).send({ message }); + } + else { + res.status(status).send({ message }); + } + } + catch (err) { + console.log(err); + return res.status(500).json({ message: err.message }); + } +} + +const registrationController = async (req, res) => { + try { + const response = await registerUser(req.body); + if (response.status === 200) { + const { status, user } = response; + const token = user.generateToken(); + //send token for automatic login + res.cookie('token', token, { + expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), + httpOnly: false, + secure: isProduction + }); + res.status(status).send({ user }); + } + else { + const { status, message } = response; + res.status(status).send({ message }); + } + } + catch (err) { + console.log(err); + return res.status(500).json({ message: err.message }); + } +}; + +const getUserController = async (req, res) => { + return res.status(200).send(req.user); +}; + +const resetPasswordRequestController = async (req, res) => { + try { + const resetService = await requestPasswordReset( + req.body.email + ); + if (resetService.link) { + return res.status(200).json(resetService); + } + else { + return res.status(400).json(resetService); + } + } + catch (e) { + console.log(e); + return res.status(400).json({ message: e.message }); + } +}; + +const resetPasswordController = async (req, res) => { + try { + const resetPasswordService = await resetPassword( + req.body.userId, + req.body.token, + req.body.password + ); + if(resetPasswordService instanceof Error) { + return res.status(400).json(resetPasswordService); + } + else { + return res.status(200).json(resetPasswordService); + } + } + catch (e) { + console.log(e); + return res.status(400).json({ message: e.message }); + } +}; + +const refreshController = async (req, res, next) => { + const { signedCookies = {} } = req; + const { refreshToken } = signedCookies; + //TODO + // if (refreshToken) { + // try { + // const payload = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET); + // const userId = payload._id; + // User.findOne({ _id: userId }).then( + // (user) => { + // if (user) { + // // Find the refresh token against the user record in database + // const tokenIndex = user.refreshToken.findIndex(item => item.refreshToken === refreshToken); + + // if (tokenIndex === -1) { + // res.statusCode = 401; + // res.send('Unauthorized'); + // } else { + // const token = req.user.generateToken(); + // // If the refresh token exists, then create new one and replace it. + // const newRefreshToken = req.user.generateRefreshToken(); + // user.refreshToken[tokenIndex] = { refreshToken: newRefreshToken }; + // user.save((err) => { + // if (err) { + // res.statusCode = 500; + // res.send(err); + // } else { + // // setTokenCookie(res, newRefreshToken); + // const user = req.user.toJSON(); + // res.status(200).send({ token, user }); + // } + // }); + // } + // } else { + // res.statusCode = 401; + // res.send('Unauthorized'); + // } + // }, + // err => next(err) + // ); + // } catch (err) { + // res.statusCode = 401; + // res.send('Unauthorized'); + // } + // } else { + // res.statusCode = 401; + // res.send('Unauthorized'); + // } +}; + +module.exports = { + getUserController, + loginController, + logoutController, + refreshController, + registrationController, + resetPasswordRequestController, + resetPasswordController, +}; \ No newline at end of file diff --git a/api/server/controllers/errorController.js b/api/server/controllers/error.controller.js similarity index 100% rename from api/server/controllers/errorController.js rename to api/server/controllers/error.controller.js diff --git a/api/server/index.js b/api/server/index.js index de49b81d1..15b8e7507 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -1,12 +1,12 @@ const express = require('express'); -const session = require('express-session'); const connectDb = require('../lib/db/connectDb'); const migrateDb = require('../lib/db/migrateDb'); const indexSync = require('../lib/db/indexSync'); const path = require('path'); const cors = require('cors'); const routes = require('./routes'); -const errorController = require('./controllers/errorController'); +const errorController = require('./controllers/error.controller'); +const passport = require('passport'); const port = process.env.PORT || 3080; const host = process.env.HOST || 'localhost'; @@ -20,44 +20,38 @@ const projectPath = path.join(__dirname, '..', '..', 'client'); const app = express(); app.use(errorController); - app.use(cors()); app.use(express.json()); + app.use(express.urlencoded({ extended: true })); app.use(express.static(path.join(projectPath, 'dist'))); app.set('trust proxy', 1); // trust first proxy - app.use( - session({ - secret: 'chatgpt-clone-random-secrect', - resave: false, - saveUninitialized: true, - cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 } // 7 days - }) - ); - - // ROUTES - - /* chore: potential redirect error here, can only comment out this block; - comment back in if using auth routes i guess */ - // app.get('/', routes.authenticatedOrRedirect, function (req, res) { - // console.log(path.join(projectPath, 'public', 'index.html')); - // res.sendFile(path.join(projectPath, 'public', 'index.html')); - // }); + app.use(cors()); + // OAUTH + app.use(passport.initialize()); + require('../strategies/jwtStrategy'); + require('../strategies/localStrategy'); + if(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) { + require('../strategies/googleStrategy'); + } + if(process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET) { + require('../strategies/facebookStrategy'); + } + app.use('/oauth', routes.oauth) // api endpoint - app.use('/api/search', routes.authenticatedOr401, routes.search); - app.use('/api/ask', routes.authenticatedOr401, routes.ask); - app.use('/api/messages', routes.authenticatedOr401, routes.messages); - app.use('/api/convos', routes.authenticatedOr401, routes.convos); - app.use('/api/presets', routes.authenticatedOr401, routes.presets); - app.use('/api/prompts', routes.authenticatedOr401, routes.prompts); - app.use('/api/tokenizer', routes.authenticatedOr401, routes.tokenizer); - app.use('/api/endpoints', routes.authenticatedOr401, routes.endpoints); + app.use('/api/auth', routes.auth); + app.use('/api/search', routes.search); + app.use('/api/ask', routes.ask); + app.use('/api/messages', routes.messages); + app.use('/api/convos', routes.convos); + app.use('/api/presets', routes.presets); + app.use('/api/prompts', routes.prompts); + app.use('/api/tokenizer', routes.tokenizer); + app.use('/api/endpoints', routes.endpoints); + - // user system - app.use('/auth', routes.auth); - app.use('/api/me', routes.me); // static files - app.get('/*', routes.authenticatedOrRedirect, function (req, res) { + app.get('/*', function (req, res) { res.sendFile(path.join(projectPath, 'dist', 'index.html')); }); @@ -71,7 +65,7 @@ const projectPath = path.join(__dirname, '..', '..', 'client'); })(); let messageCount = 0; -process.on('uncaughtException', err => { +process.on('uncaughtException', (err) => { if (!err.message.includes('fetch failed')) { console.error('There was an uncaught error:', err.message); } diff --git a/api/server/routes/ask/addToCache.js b/api/server/routes/ask/addToCache.js index e2214ba17..3f07cdf83 100644 --- a/api/server/routes/ask/addToCache.js +++ b/api/server/routes/ask/addToCache.js @@ -1,6 +1,5 @@ const Keyv = require('keyv'); const { KeyvFile } = require('keyv-file'); -const { saveMessage } = require('../../../models'); const addToCache = async ({ endpoint, endpointOption, userMessage, responseMessage }) => { try { diff --git a/api/server/routes/ask/askBingAI.js b/api/server/routes/ask/askBingAI.js index 5d21ec4d2..2d1f92418 100644 --- a/api/server/routes/ask/askBingAI.js +++ b/api/server/routes/ask/askBingAI.js @@ -4,8 +4,9 @@ const router = express.Router(); const { titleConvo, askBing } = require('../../../app'); const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models'); const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); +const requireJwtAuth = require('../../../middleware/requireJwtAuth'); -router.post('/', async (req, res) => { +router.post('/', requireJwtAuth, async (req, res) => { const { endpoint, text, @@ -62,7 +63,7 @@ router.post('/', async (req, res) => { if (!overrideParentMessageId) { await saveMessage(userMessage); - await saveConvo(req?.session?.user?.username, { + await saveConvo(req.user.id, { ...userMessage, ...endpointOption, conversationId, @@ -205,7 +206,7 @@ const ask = async ({ conversationUpdate.invocationId = response.invocationId; } - await saveConvo(req?.session?.user?.username, conversationUpdate); + await saveConvo(req.user.id, conversationUpdate); conversationId = newConversationId; // STEP3 update the user message @@ -218,9 +219,9 @@ const ask = async ({ userMessageId = newUserMassageId; sendMessage(res, { - title: await getConvoTitle(req?.session?.user?.username, conversationId), + title: await getConvoTitle(req.user.id, conversationId), final: true, - conversation: await getConvo(req?.session?.user?.username, conversationId), + conversation: await getConvo(req.user.id, conversationId), requestMessage: userMessage, responseMessage: responseMessage }); @@ -229,7 +230,7 @@ const ask = async ({ if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: responseMessage }); - await saveConvo(req?.session?.user?.username, { + await saveConvo(req.user.id, { conversationId: conversationId, title }); diff --git a/api/server/routes/ask/askChatGPTBrowser.js b/api/server/routes/ask/askChatGPTBrowser.js index 25b6f6b73..16c67005f 100644 --- a/api/server/routes/ask/askChatGPTBrowser.js +++ b/api/server/routes/ask/askChatGPTBrowser.js @@ -5,8 +5,9 @@ const { getChatGPTBrowserModels } = require('../endpoints'); const { browserClient } = require('../../../app/'); const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models'); const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); +const requireJwtAuth = require('../../../middleware/requireJwtAuth'); -router.post('/', async (req, res) => { +router.post('/', requireJwtAuth, async (req, res) => { const { endpoint, text, @@ -49,7 +50,7 @@ router.post('/', async (req, res) => { if (!overrideParentMessageId) { await saveMessage(userMessage); - await saveConvo(req?.session?.user?.username, { + await saveConvo(req.user.id, { ...userMessage, ...endpointOption, conversationId, @@ -81,6 +82,7 @@ const ask = async ({ res }) => { let { text, parentMessageId: userParentMessageId, messageId: userMessageId } = userMessage; + const userId = req.user.id; res.writeHead(200, { Connection: 'keep-alive', @@ -121,7 +123,8 @@ const ask = async ({ conversationId, ...endpointOption, onProgress: progressCallback.call(null, { res, text }), - abortController + abortController, + userId }); console.log('CLIENT RESPONSE', response); @@ -168,7 +171,7 @@ const ask = async ({ }; } - await saveConvo(req?.session?.user?.username, conversationUpdate); + await saveConvo(req.user.id, conversationUpdate); conversationId = newConversationId; // STEP3 update the user message @@ -181,9 +184,9 @@ const ask = async ({ userMessageId = newUserMassageId; sendMessage(res, { - title: await getConvoTitle(req?.session?.user?.username, conversationId), + title: await getConvoTitle(req.user.id, conversationId), final: true, - conversation: await getConvo(req?.session?.user?.username, conversationId), + conversation: await getConvo(req.user.id, conversationId), requestMessage: userMessage, responseMessage: responseMessage }); @@ -192,7 +195,7 @@ const ask = async ({ if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { // const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: responseMessage }); const title = await response.details.title; - await saveConvo(req?.session?.user?.username, { + await saveConvo(req.user.id, { conversationId: conversationId, title }); diff --git a/api/server/routes/ask/askOpenAI.js b/api/server/routes/ask/askOpenAI.js index 550518c2c..642ac7863 100644 --- a/api/server/routes/ask/askOpenAI.js +++ b/api/server/routes/ask/askOpenAI.js @@ -6,10 +6,11 @@ const { getOpenAIModels } = require('../endpoints'); const { titleConvo, askClient } = require('../../../app/'); const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models'); const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); +const requireJwtAuth = require('../../../middleware/requireJwtAuth'); const abortControllers = new Map(); -router.post('/abort', async (req, res) => { +router.post('/abort', requireJwtAuth, async (req, res) => { const { abortKey } = req.body; console.log(`req.body`, req.body); if (!abortControllers.has(abortKey)) { @@ -26,7 +27,7 @@ router.post('/abort', async (req, res) => { res.send(JSON.stringify(ret)); }); -router.post('/', async (req, res) => { +router.post('/', requireJwtAuth, async (req, res) => { const { endpoint, text, @@ -74,7 +75,7 @@ router.post('/', async (req, res) => { if (!overrideParentMessageId) { await saveMessage(userMessage); - await saveConvo(req?.session?.user?.username, { + await saveConvo(req.user.id, { ...userMessage, ...endpointOption, conversationId, @@ -106,7 +107,7 @@ const ask = async ({ res }) => { let { text, parentMessageId: userParentMessageId, messageId: userMessageId } = userMessage; - + const userId = req.user.id; let responseMessageId = crypto.randomUUID(); res.writeHead(200, { @@ -159,9 +160,9 @@ const ask = async ({ await addToCache({ endpoint: 'openAI', endpointOption, userMessage, responseMessage }); return { - title: await getConvoTitle(req?.session?.user?.username, conversationId), + title: await getConvoTitle(req.user.id, conversationId), final: true, - conversation: await getConvo(req?.session?.user?.username, conversationId), + conversation: await getConvo(req.user.id, conversationId), requestMessage: userMessage, responseMessage: responseMessage }; @@ -179,7 +180,8 @@ const ask = async ({ text, parentMessageId: overrideParentMessageId || userMessageId }), - abortController + abortController, + userId }); abortControllers.delete(abortKey); @@ -225,7 +227,7 @@ const ask = async ({ }; } - await saveConvo(req?.session?.user?.username, conversationUpdate); + await saveConvo(req.user.id, conversationUpdate); conversationId = newConversationId; // STEP3 update the user message @@ -238,9 +240,9 @@ const ask = async ({ userMessageId = newUserMassageId; sendMessage(res, { - title: await getConvoTitle(req?.session?.user?.username, conversationId), + title: await getConvoTitle(req.user.id, conversationId), final: true, - conversation: await getConvo(req?.session?.user?.username, conversationId), + conversation: await getConvo(req.user.id, conversationId), requestMessage: userMessage, responseMessage: responseMessage }); @@ -248,7 +250,7 @@ const ask = async ({ if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: responseMessage }); - await saveConvo(req?.session?.user?.username, { + await saveConvo(req.user.id, { conversationId: conversationId, title }); diff --git a/api/server/routes/auth.js b/api/server/routes/auth.js index 56555db51..588a9eb83 100644 --- a/api/server/routes/auth.js +++ b/api/server/routes/auth.js @@ -1,57 +1,25 @@ const express = require('express'); +const { + resetPasswordRequestController, + resetPasswordController, + getUserController, + loginController, + logoutController, + refreshController, + registrationController, +} = require('../controllers/auth.controller'); +const requireJwtAuth = require('../../middleware/requireJwtAuth'); +const requireLocalAuth = require('../../middleware/requireLocalAuth'); + const router = express.Router(); -const authYourLogin = require('./authYourLogin'); -const userSystemEnabled = !!process.env.ENABLE_USER_SYSTEM || false; -router.get('/login', function (req, res) { - if (userSystemEnabled) { - res.redirect('/auth/your_login_page'); - } else { - res.redirect('/'); - } -}); +//Local +router.get('/user', requireJwtAuth, getUserController); +router.post('/logout', requireJwtAuth, logoutController); +router.post('/login', requireLocalAuth, loginController); +router.post('/refresh', requireJwtAuth, refreshController); +router.post('/register', registrationController); +router.post('/requestPasswordReset', resetPasswordRequestController); +router.post('/resetPassword', resetPasswordController); -router.get('/logout', function (req, res) { - // clear the session - req.session.user = null; - - req.session.save(function () { - if (userSystemEnabled) { - res.redirect('/auth/your_login_page/logout'); - } else { - res.redirect('/'); - } - }); -}); - -const authenticatedOr401 = (req, res, next) => { - if (userSystemEnabled) { - const user = req?.session?.user; - - if (user) { - next(); - } else { - res.status(401).end(); - } - } else { - next(); - } -}; - -const authenticatedOrRedirect = (req, res, next) => { - if (userSystemEnabled) { - const user = req?.session?.user; - - if (user) { - next(); - } else { - res.redirect('/auth/login'); - } - } else next(); -}; - -if (userSystemEnabled) { - router.use('/your_login_page', authYourLogin); -} - -module.exports = { router, authenticatedOr401, authenticatedOrRedirect }; +module.exports = router; diff --git a/api/server/routes/authYourLogin.js b/api/server/routes/authYourLogin.js deleted file mode 100644 index 21aa9e19e..000000000 --- a/api/server/routes/authYourLogin.js +++ /dev/null @@ -1,44 +0,0 @@ -const express = require('express'); -const router = express.Router(); - -// WARNING! -// THIS IS NOT A READY TO USE USER SYSTEM -// PLEASE IMPLEMENT YOUR OWN USER SYSTEM - -const userSystemEnabled = process.env.ENABLE_USER_SYSTEM || false; - -// Logout -router.get('/logout', (req, res) => { - // Do anything you want - console.warn('logout not implemented!'); - - // finish - res.redirect('/'); -}); - -// Login -router.get('/', async (req, res) => { - // Do anything you want - console.warn('login not implemented! Automatic passed as sample user'); - - // save the user info into session - // username will be used in db - // display will be used in UI - if (userSystemEnabled) { - req.session.user = { - username: null, // was 'sample_user', but would break previous relationship with previous conversations before v0.1.0 - display: 'Sample User' - }; - } - - req.session.save(function (error) { - if (error) { - console.log(error); - res.send(`

Login Failed. An error occurred. Please see the server logs for details.

`); - } else { - res.redirect('/'); - } - }); -}); - -module.exports = router; diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 03ba85a1a..5eb56a2df 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -1,24 +1,23 @@ const express = require('express'); const router = express.Router(); -const { titleConvo } = require('../../app/'); -const { getConvo, saveConvo, getConvoTitle } = require('../../models'); +const { getConvo, saveConvo } = require('../../models'); const { getConvosByPage, deleteConvos } = require('../../models/Conversation'); -const { getMessages } = require('../../models/Message'); +const requireJwtAuth = require('../../middleware/requireJwtAuth'); -router.get('/', async (req, res) => { +router.get('/', requireJwtAuth, async (req, res) => { const pageNumber = req.query.pageNumber || 1; - res.status(200).send(await getConvosByPage(req?.session?.user?.username, pageNumber)); + res.status(200).send(await getConvosByPage(req.user.id, pageNumber)); }); -router.get('/:conversationId', async (req, res) => { +router.get('/:conversationId', requireJwtAuth, async (req, res) => { const { conversationId } = req.params; - const convo = await getConvo(req?.session?.user?.username, conversationId); + const convo = await getConvo(req.user.id, conversationId); if (convo) res.status(200).send(convo.toObject()); else res.status(404).end(); }); -router.post('/clear', async (req, res) => { +router.post('/clear', requireJwtAuth, async (req, res) => { let filter = {}; const { conversationId, source } = req.body.arg; if (conversationId) { @@ -32,7 +31,7 @@ router.post('/clear', async (req, res) => { } try { - const dbResponse = await deleteConvos(req?.session?.user?.username, filter); + const dbResponse = await deleteConvos(req.user.id, filter); res.status(201).send(dbResponse); } catch (error) { console.error(error); @@ -40,11 +39,11 @@ router.post('/clear', async (req, res) => { } }); -router.post('/update', async (req, res) => { +router.post('/update', requireJwtAuth, async (req, res) => { const update = req.body.arg; try { - const dbResponse = await saveConvo(req?.session?.user?.username, update); + const dbResponse = await saveConvo(req.user.id, update); res.status(201).send(dbResponse); } catch (error) { console.error(error); diff --git a/api/server/routes/index.js b/api/server/routes/index.js index 5399cfdaa..f90f55e1a 100644 --- a/api/server/routes/index.js +++ b/api/server/routes/index.js @@ -5,9 +5,9 @@ const presets = require('./presets'); const prompts = require('./prompts'); const search = require('./search'); const tokenizer = require('./tokenizer'); -const me = require('./me'); +const auth = require('./auth'); +const oauth = require('./oauth'); const { router: endpoints } = require('./endpoints'); -const { router: auth, authenticatedOr401, authenticatedOrRedirect } = require('./auth'); module.exports = { search, @@ -17,9 +17,7 @@ module.exports = { presets, prompts, auth, + oauth, tokenizer, - me, endpoints, - authenticatedOr401, - authenticatedOrRedirect }; diff --git a/api/server/routes/me.js b/api/server/routes/me.js deleted file mode 100644 index 3b12b4854..000000000 --- a/api/server/routes/me.js +++ /dev/null @@ -1,16 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const userSystemEnabled = !!process.env.ENABLE_USER_SYSTEM || false; - -router.get('/', function (req, res) { - if (userSystemEnabled) { - const user = req?.session?.user; - - if (user) res.send(JSON.stringify({ username: user?.username, display: user?.display })); - else res.send(JSON.stringify(null)); - } else { - res.send(JSON.stringify({ username: 'anonymous_user', display: 'Anonymous User' })); - } -}); - -module.exports = router; diff --git a/api/server/routes/messages.js b/api/server/routes/messages.js index be34a062e..a13b4272b 100644 --- a/api/server/routes/messages.js +++ b/api/server/routes/messages.js @@ -1,8 +1,9 @@ const express = require('express'); const router = express.Router(); const { getMessages } = require('../../models/Message'); +const requireJwtAuth = require('../../middleware/requireJwtAuth'); -router.get('/:conversationId', async (req, res) => { +router.get('/:conversationId', requireJwtAuth, async (req, res) => { const { conversationId } = req.params; res.status(200).send(await getMessages({ conversationId })); }); diff --git a/api/server/routes/oauth.js b/api/server/routes/oauth.js new file mode 100644 index 000000000..59d6148d6 --- /dev/null +++ b/api/server/routes/oauth.js @@ -0,0 +1,64 @@ +const passport = require('passport'); +const express = require('express'); + +const router = express.Router(); + +const isProduction = process.env.NODE_ENV === 'production'; +const clientUrl = isProduction ? process.env.CLIENT_URL_PROD : process.env.CLIENT_URL_DEV; + +// Social +router.get( + '/google', + passport.authenticate('google', { + scope: ['openid', 'profile', 'email'], + session: false + }) +); + +router.get( + '/google/callback', + passport.authenticate('google', { + failureRedirect: `${clientUrl}/login`, + failureMessage: true, + session: false, + scope: ['openid', 'profile', 'email'] + }), + (req, res) => { + const token = req.user.generateToken(); + res.cookie('token', token, { + expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), + httpOnly: false, + secure: isProduction + }); + res.redirect(clientUrl); + } +); + +router.get( + '/facebook', + passport.authenticate('facebook', { + scope: ['public_profile', 'email'], + session: false + }) +); + +router.get( + '/facebook/callback', + passport.authenticate('facebook', { + failureRedirect: `${clientUrl}/login`, + failureMessage: true, + session: false, + scope: ['public_profile', 'email'] + }), + (req, res) => { + const token = req.user.generateToken(); + res.cookie('token', token, { + expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), + httpOnly: false, + secure: isProduction + }); + res.redirect(clientUrl); + } +); + +module.exports = router; \ No newline at end of file diff --git a/api/server/routes/presets.js b/api/server/routes/presets.js index 09146be11..235a1fe31 100644 --- a/api/server/routes/presets.js +++ b/api/server/routes/presets.js @@ -2,23 +2,24 @@ const express = require('express'); const router = express.Router(); const { getPresets, savePreset, deletePresets } = require('../../models'); const crypto = require('crypto'); +const requireJwtAuth = require('../../middleware/requireJwtAuth'); -router.get('/', async (req, res) => { - const presets = (await getPresets(req?.session?.user?.username)).map((preset) => { +router.get('/', requireJwtAuth, async (req, res) => { + const presets = (await getPresets(req.user.id)).map((preset) => { return preset.toObject(); }); res.status(200).send(presets); }); -router.post('/', async (req, res) => { +router.post('/', requireJwtAuth, async (req, res) => { const update = req.body || {}; update.presetId = update?.presetId || crypto.randomUUID(); try { - await savePreset(req?.session?.user?.username, update); + await savePreset(req.user.id, update); - const presets = (await getPresets(req?.session?.user?.username)).map((preset) => { + const presets = (await getPresets(req.user.id)).map((preset) => { return preset.toObject(); }); res.status(201).send(presets); @@ -28,7 +29,7 @@ router.post('/', async (req, res) => { } }); -router.post('/delete', async (req, res) => { +router.post('/delete', requireJwtAuth, async (req, res) => { let filter = {}; const { presetId } = req.body.arg || {}; @@ -37,9 +38,9 @@ router.post('/delete', async (req, res) => { console.log('delete preset filter', filter); try { - await deletePresets(req?.session?.user?.username, filter); + await deletePresets(req.user.id, filter); - const presets = (await getPresets(req?.session?.user?.username)).map(preset => preset.toObject()); + const presets = (await getPresets(req.user.id)).map(preset => preset.toObject()); // console.log('delete preset response', presets); res.status(201).send(presets); diff --git a/api/server/routes/search.js b/api/server/routes/search.js index 1cc360283..91ebb718b 100644 --- a/api/server/routes/search.js +++ b/api/server/routes/search.js @@ -5,6 +5,8 @@ const { Message } = require('../../models/Message'); const { Conversation, getConvosQueried } = require('../../models/Conversation'); const { reduceHits } = require('../../lib/utils/reduceHits'); const { cleanUpPrimaryKeyValue } = require('../../lib/utils/misc'); +const requireJwtAuth = require('../../middleware/requireJwtAuth'); + const cache = new Map(); router.get('/sync', async function (req, res) { @@ -13,9 +15,9 @@ router.get('/sync', async function (req, res) { res.send('synced'); }); -router.get('/', async function (req, res) { +router.get('/', requireJwtAuth, async function (req, res) { try { - let user = req?.session?.user?.username; + let user = req.user.id; user = user ?? null; const { q } = req.query; const pageNumber = req.query.pageNumber || 1; diff --git a/api/server/routes/tokenizer.js b/api/server/routes/tokenizer.js index a5b95530d..743d64963 100644 --- a/api/server/routes/tokenizer.js +++ b/api/server/routes/tokenizer.js @@ -4,8 +4,9 @@ const { Tiktoken } = require('@dqbd/tiktoken/lite'); const { load } = require('@dqbd/tiktoken/load'); const registry = require('@dqbd/tiktoken/registry.json'); const models = require('@dqbd/tiktoken/model_to_encoding.json'); +const requireJwtAuth = require('../../middleware/requireJwtAuth'); -router.post('/', async (req, res) => { +router.post('/', requireJwtAuth, async (req, res) => { try { const { arg } = req.body; diff --git a/api/server/services/auth.service.js b/api/server/services/auth.service.js new file mode 100644 index 000000000..a82ba0fd6 --- /dev/null +++ b/api/server/services/auth.service.js @@ -0,0 +1,197 @@ +const User = require('../../models/User'); +const Token = require('../../models/schema/tokenSchema'); +const sendEmail = require('../../utils/sendEmail'); +const crypto = require('crypto'); +const bcrypt = require('bcrypt'); +const DebugControl = require('../../utils/debug.js'); +const Joi = require('joi'); +const { registerSchema } = require('../../strategies/validators'); +const migrateDataToFirstUser = require('../../utils/migrateDataToFirstUser'); + +function log({ title, parameters }) { + DebugControl.log.functionName(title); + DebugControl.log.parameters(parameters); +} + +const isProduction = process.env.NODE_ENV === 'production'; +const clientUrl = isProduction ? process.env.CLIENT_URL_PROD : process.env.CLIENT_URL_DEV; + +const loginUser = async (user) => { + // const refreshToken = req.user.generateRefreshToken(); + const dbUser = await User.findById(user._id); + //todo: save refresh token + + return dbUser; +}; + +const logoutUser = async (user, refreshToken) => { + User.findById(user._id).then((user) => { + const tokenIndex = user.refreshToken.findIndex(item => item.refreshToken === refreshToken); + + if (tokenIndex !== -1) { + user.refreshToken.id(user.refreshToken[tokenIndex]._id).remove(); + } + + user.save((err) => { + if (err) { + return { status: 500, message: err.message }; + } else { + //res.clearCookie('refreshToken', COOKIE_OPTIONS); + // removeTokenCookie(res); + return { status: 200, message: 'Logout successful' }; + } + }); + }); + return { status: 200, message: 'Logout successful' }; +}; + +const registerUser = async (user) => { + let response = {}; + const { error } = Joi.validate(user, registerSchema); + if (error) { + log({ + title: 'Route: register - Joi Validation Error', + parameters: [ + { name: 'Request params:', value: user }, + { name: 'Validation error:', value: error.details } + ] + }); + response = { status: 422, message: error.details[0].message }; + return response; + } + + const { email, password, name, username } = user; + + try { + const existingUser = await User.findOne({ email }); + + if (existingUser) { + log({ + title: 'Register User - Email in use', + parameters: [ + { name: 'Request params:', value: user }, + { name: 'Existing user:', value: existingUser } + ] + }); + response = { status: 422, message: 'Email is in use' }; + return response; + } + + //determine if this is the first registered user (not counting anonymous_user) + const isFirstRegisteredUser = await User.countDocuments({}) === 0; + + try { + const newUser = await new User({ + provider: 'local', + email, + password, + username, + name, + avatar: null, + role: isFirstRegisteredUser ? 'ADMIN' : 'USER', + }); + + // todo: implement refresh token + // const refreshToken = newUser.generateRefreshToken(); + // newUser.refreshToken.push({ refreshToken }); + bcrypt.genSalt(10, (err, salt) => { + bcrypt.hash(newUser.password, salt, (errh, hash) => { + if (err) { + console.log(err); + } + // set pasword to hash + newUser.password = hash; + newUser.save(); + }); + }); + console.log('newUser', newUser) + if (isFirstRegisteredUser) { + migrateDataToFirstUser(newUser); + // console.log(migrate); + } + response = { status: 200, user: newUser }; + return response; + } catch (err) { + response = { status: 500, message: err.message }; + return response; + } + } catch (err) { + response = { status: 500, message: err.message }; + return response; + } +}; + +const requestPasswordReset = async (email) => { + const user = await User.findOne({ email }); + if (!user) { + return new Error('Email does not exist'); + } + + let token = await Token.findOne({ userId: user._id }); + if (token) await token.deleteOne(); + + let resetToken = crypto.randomBytes(32).toString('hex'); + const hash = await bcrypt.hash(resetToken, 10); + + await new Token({ + userId: user._id, + token: hash, + createdAt: Date.now() + }).save(); + + const link = `${clientUrl}/reset-password?token=${resetToken}&userId=${user._id}`; + + sendEmail( + user.email, + 'Password Reset Request', + { + name: user.name, + link: link + }, + './template/requestResetPassword.handlebars' + ); + return { link }; +}; + +const resetPassword = async (userId, token, password) => { + let passwordResetToken = await Token.findOne({ userId }); + + if (!passwordResetToken) { + return new Error('Invalid or expired password reset token'); + } + + const isValid = await bcrypt.compare(token, passwordResetToken.token); + + if (!isValid) { + return new Error('Invalid or expired password reset token'); + } + + const hash = await bcrypt.hash(password, 10); + + await User.updateOne({ _id: userId }, { $set: { password: hash } }, { new: true }); + + const user = await User.findById({ _id: userId }); + + sendEmail( + user.email, + 'Password Reset Successfnodeully', + { + name: user.name + }, + './template/resetPassword.handlebars' + ); + + await passwordResetToken.deleteOne(); + + return { message: 'Password reset was successful' }; +}; + + +module.exports = { + // signup, + registerUser, + loginUser, + logoutUser, + requestPasswordReset, + resetPassword, +}; diff --git a/api/strategies/facebookStrategy.js b/api/strategies/facebookStrategy.js new file mode 100644 index 000000000..010683c46 --- /dev/null +++ b/api/strategies/facebookStrategy.js @@ -0,0 +1,60 @@ +const passport = require('passport'); +const FacebookStrategy = require('passport-facebook').Strategy; +const User = require('../models/User'); + +const serverUrl = + process.env.NODE_ENV === 'production' ? process.env.SERVER_URL_PROD : process.env.SERVER_URL_DEV; + +// facebook strategy +const facebookLogin = new FacebookStrategy( + { + clientID: process.env.FACEBOOK_APP_ID, + clientSecret: process.env.FACEBOOK_SECRET, + callbackURL: `${serverUrl}${process.env.FACEBOOK_CALLBACK_URL}`, + proxy: true, + // profileFields: [ + // 'id', + // 'email', + // 'gender', + // 'profileUrl', + // 'displayName', + // 'locale', + // 'name', + // 'timezone', + // 'updated_time', + // 'verified', + // 'picture.type(large)' + // ] + }, + async (accessToken, refreshToken, profile, done) => { + console.log('facebookLogin => profile', profile); + try { + const oldUser = await User.findOne({ email: profile.emails[0].value }); + + if (oldUser) { + console.log('FACEBOOK LOGIN => found user', oldUser); + return done(null, oldUser); + } + } catch (err) { + console.log(err); + } + + // register user + try { + const newUser = await new User({ + provider: 'facebook', + facebookId: profile.id, + username: profile.name.givenName + profile.name.familyName, + email: profile.emails[0].value, + name: profile.displayName, + avatar: profile.photos[0].value + }).save(); + + done(null, newUser); + } catch (err) { + console.log(err); + } + } +); + +passport.use(facebookLogin); diff --git a/api/strategies/googleStrategy.js b/api/strategies/googleStrategy.js new file mode 100644 index 000000000..768c0991b --- /dev/null +++ b/api/strategies/googleStrategy.js @@ -0,0 +1,44 @@ +const passport = require('passport'); +const { Strategy: GoogleStrategy } = require('passport-google-oauth20'); + +const User = require('../models/User'); + +const serverUrl = + process.env.NODE_ENV === 'production' ? process.env.SERVER_URL_PROD : process.env.SERVER_URL_DEV; + +// google strategy +const googleLogin = new GoogleStrategy( + { + clientID: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + callbackURL: `${serverUrl}${process.env.GOOGLE_CALLBACK_URL}`, + proxy: true + }, + async (accessToken, refreshToken, profile, cb) => { + try { + const oldUser = await User.findOne({ email: profile.emails[0].value }); + if (oldUser) { + return cb(null, oldUser); + } + } catch (err) { + console.log(err); + } + + try { + const newUser = await new User({ + provider: 'google', + googleId: profile.id, + username: profile.name.givenName + profile.name.familyName, + email: profile.emails[0].value, + emailVerified: profile.emails[0].verified, + name: `${profile.name.givenName} ${profile.name.familyName}`, + avatar: profile.photos[0].value + }).save(); + cb(null, newUser); + } catch (err) { + console.log(err); + } + } +); + +passport.use(googleLogin); diff --git a/api/strategies/jwtStrategy.js b/api/strategies/jwtStrategy.js new file mode 100644 index 000000000..f94749053 --- /dev/null +++ b/api/strategies/jwtStrategy.js @@ -0,0 +1,29 @@ +const passport = require('passport'); +const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); +const User = require('../models/User'); + +const isProduction = process.env.NODE_ENV === 'production'; +const secretOrKey = isProduction ? process.env.JWT_SECRET_PROD : process.env.JWT_SECRET_DEV; + +// JWT strategy +const jwtLogin = new JwtStrategy( + { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey + }, + async (payload, done) => { + try { + const user = await User.findById(payload.id); + if (user) { + done(null, user); + } else { + console.log('JwtStrategy => no user found'); + done(null, false); + } + } catch (err) { + done(err, false); + } + } +); + +passport.use(jwtLogin); diff --git a/api/strategies/localStrategy.js b/api/strategies/localStrategy.js new file mode 100644 index 000000000..b073546b2 --- /dev/null +++ b/api/strategies/localStrategy.js @@ -0,0 +1,68 @@ +const passport = require('passport'); +const PassportLocalStrategy = require('passport-local').Strategy; +const Joi = require('joi'); + +const User = require('../models/User'); +const { loginSchema } = require('./validators'); +const DebugControl = require('../utils/debug.js'); + +const passportLogin = new PassportLocalStrategy( + { + usernameField: 'email', + passwordField: 'password', + session: false, + passReqToCallback: true + }, + async (req, email, password, done) => { + const { error } = Joi.validate(req.body, loginSchema); + if (error) { + log({ + title: 'Passport Local Strategy - Validation Error', + parameters: [{ name: 'req.body', value: req.body }] + }); + return done(null, false, { message: error.details[0].message }); + } + + try { + const user = await User.findOne({ email: email.trim() }); + if (!user) { + log({ + title: 'Passport Local Strategy - User Not Found', + parameters: [{ name: 'email', value: email }] + }); + return done(null, false, { message: 'Email does not exists.' }); + } + + user.comparePassword(password, function (err, isMatch) { + if (err) { + log({ + title: 'Passport Local Strategy - Compare password error', + parameters: [{ name: 'error', value: err }] + }); + return done(err); + } + if (!isMatch) { + log({ + title: 'Passport Local Strategy - Password does not match', + parameters: [{ name: 'isMatch', value: isMatch }] + }); + return done(null, false, { message: 'Incorrect password.' }); + } + + return done(null, user); + }); + } catch (err) { + return done(err); + } + } +); + +passport.use(passportLogin); + +function log({ title, parameters }) { + DebugControl.log.functionName(title); + if (parameters) { + DebugControl.log.parameters(parameters); + } +} + diff --git a/api/strategies/validators.js b/api/strategies/validators.js new file mode 100644 index 000000000..272b27b0f --- /dev/null +++ b/api/strategies/validators.js @@ -0,0 +1,24 @@ +const Joi = require('joi'); + +const loginSchema = Joi.object().keys({ + email: Joi.string().trim().email().required(), + password: Joi.string().trim().min(6).max(20).required() +}); + +const registerSchema = Joi.object().keys({ + name: Joi.string().trim().min(2).max(30).required(), + username: Joi.string() + .trim() + .min(2) + .max(20) + .regex(/^[a-zA-Z0-9_]+$/) + .required(), + email: Joi.string().trim().email().required(), + password: Joi.string().trim().min(6).max(20).required(), + confirm_password: Joi.string().trim().min(6).max(20).required() +}); + +module.exports = { + loginSchema, + registerSchema +}; \ No newline at end of file diff --git a/api/utils/debug.js b/api/utils/debug.js new file mode 100644 index 000000000..71c45d877 --- /dev/null +++ b/api/utils/debug.js @@ -0,0 +1,46 @@ +const levels = { + NONE: 0, + LOW: 1, + MEDIUM: 2, + HIGH: 3 +}; + +let level = levels.HIGH; + +module.exports = { + levels, + setLevel: (l) => (level = l), + log: { + parameters: (parameters) => { + if (levels.HIGH > level) return; + console.group(); + parameters.forEach((p) => console.log(`${p.name}:`, p.value)); + console.groupEnd(); + }, + functionName: (name) => { + if (levels.MEDIUM > level) return; + console.log(`\nEXECUTING: ${name}\n`); + }, + flow: (flow) => { + if (levels.LOW > level) return; + console.log(`\n\n\nBEGIN FLOW: ${flow}\n\n\n`); + }, + variable: ({ name, value }) => { + if (levels.HIGH > level) return; + console.group(); + console.group(); + console.log(`VARIABLE ${name}:`, value); + console.groupEnd(); + console.groupEnd(); + }, + request: () => (req, res, next) => { + if (levels.HIGH > level) return next(); + console.log('Hit URL', req.url, 'with following:'); + console.group(); + console.log('Query:', req.query); + console.log('Body:', req.body); + console.groupEnd(); + return next(); + } + } +}; diff --git a/api/utils/emails/passwordReset.handlebars b/api/utils/emails/passwordReset.handlebars new file mode 100644 index 000000000..2d0d5426c --- /dev/null +++ b/api/utils/emails/passwordReset.handlebars @@ -0,0 +1,11 @@ + + + + + +

Hi {{name}},

+

Your password has been changed successfully.

+ + \ No newline at end of file diff --git a/api/utils/emails/requestPasswordReset.handlebars b/api/utils/emails/requestPasswordReset.handlebars new file mode 100644 index 000000000..1bf9853c6 --- /dev/null +++ b/api/utils/emails/requestPasswordReset.handlebars @@ -0,0 +1,13 @@ + + + + + +

Hi {{name}},

+

You have requested to reset your password.

+

Please click the link below to reset your password.

+ Reset Password + + \ No newline at end of file diff --git a/api/utils/migrateDataToFirstUser.js b/api/utils/migrateDataToFirstUser.js new file mode 100644 index 000000000..efa74a9ac --- /dev/null +++ b/api/utils/migrateDataToFirstUser.js @@ -0,0 +1,30 @@ +const Conversation = require('../models/schema/convoSchema'); +const Preset = require('../models/schema/presetSchema'); + +const migrateConversations = async (userId) => { + try { + return await Conversation.updateMany({ user: null }, { $set: { user: userId }}).exec(); + } catch (error) { + console.log(error); + return { message: 'Error saving conversation' }; + } +} + +const migratePresets = async (userId) => { + try { + return await Preset.updateMany({ user: null }, { $set: { user: userId }}).exec(); + } catch (error) { + console.log(error); + return { message: 'Error saving conversation' }; + } +} + +const migrateDataToFirstUser = async (user) => { + const conversations = await migrateConversations(user.id); + console.log(conversations); + const presets = await migratePresets(user.id); + console.log(presets); +} + + +module.exports = migrateDataToFirstUser; \ No newline at end of file diff --git a/api/utils/sendEmail.js b/api/utils/sendEmail.js new file mode 100644 index 000000000..ff0de97ae --- /dev/null +++ b/api/utils/sendEmail.js @@ -0,0 +1,54 @@ +const nodemailer = require("nodemailer"); +const handlebars = require("handlebars"); +const fs = require("fs"); +const path = require("path"); + +const sendEmail = async (email, subject, payload, template) => { + try { + // create reusable transporter object using the default SMTP transport + const transporter = nodemailer.createTransport({ + host: process.env.EMAIL_HOST, + port: 465, + auth: { + user: process.env.EMAIL_USERNAME, + pass: process.env.EMAIL_PASSWORD, + }, + }); + + const source = fs.readFileSync(path.join(__dirname, template), "utf8"); + const compiledTemplate = handlebars.compile(source); + const options = () => { + return { + from: process.env.FROM_EMAIL, + to: email, + subject: subject, + html: compiledTemplate(payload), + }; + }; + + // Send email + transporter.sendMail(options(), (error, info) => { + if (error) { + return error; + } else { + return res.status(200).json({ + success: true, + }); + } + }); + } catch (error) { + return error; + } +}; + +/* +Example: +sendEmail( + "youremail@gmail.com, + "Email subject", + { name: "Eze" }, + "./templates/layouts/main.handlebars" +); +*/ + +module.exports = sendEmail; \ No newline at end of file diff --git a/client/.env.example b/client/.env.example new file mode 100644 index 000000000..91c12acd6 --- /dev/null +++ b/client/.env.example @@ -0,0 +1,16 @@ +########################### +# Server URL configuration: +########################### + +# The social login domain uses this to redirect to localhost:3080 when you run the app in dev mode with Vite. +# Use your domain name as the Prod URL when you deploy the app to a live domain. +# Please note that: +# Social login features will not work if you run the build version on port 3080 locally after modifying the Prod URL +VITE_SERVER_URL_DEV=http://localhost:3080 +VITE_SERVER_URL_PROD=http://localhost:3080 + +# Enable Social Login +# This enables/disables the Login with Google button on the login page. +# Set to true if you have registered the app with google cloud services +# and have set the GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in the /api/.env file +VITE_SHOW_GOOGLE_LOGIN_OPTION=false \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 27846da76..973270f4d 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -6,9 +6,14 @@ "packages": { "": { "name": "chatgpt-clone", - "version": "0.3.2", + "version": "0.3.3", "license": "ISC", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-brands-svg-icons": "^6.4.0", + "@fortawesome/free-regular-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@fortawesome/react-fontawesome": "^0.2.0", "@headlessui/react": "^1.7.13", "@radix-ui/react-alert-dialog": "^1.0.2", "@radix-ui/react-checkbox": "^1.0.3", @@ -18,6 +23,7 @@ "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-slider": "^1.1.1", "@radix-ui/react-tabs": "^1.0.3", + "@tailwindcss/forms": "^0.5.3", "@tanstack/react-query": "^4.28.0", "@types/jest": "^29.5.0", "@types/node": "^18.15.10", @@ -39,6 +45,7 @@ "rc-input-number": "^7.4.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.43.9", "react-lazy-load": "^4.0.1", "react-markdown": "^8.0.6", "react-router-dom": "^6.9.0", @@ -96,10 +103,7 @@ "ts-loader": "^9.4.2", "typescript": "^4.9.5", "vite": "^4.2.1", - "vite-plugin-html": "^3.2.0", - "webpack": "^5.77.0", - "webpack-cli": "^5.0.1", - "webpack-dev-server": "^4.11.1" + "vite-plugin-html": "^3.2.0" } }, "node_modules/@ampproject/remapping": { @@ -2377,14 +2381,6 @@ "postcss-selector-parser": "^6.0.10" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/@esbuild/win32-x64": { "version": "0.17.15", "cpu": [ @@ -2499,6 +2495,75 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", + "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", + "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz", + "integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz", + "integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz", + "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@headlessui/react": { "version": "1.7.13", "license": "MIT", @@ -2718,11 +2783,6 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "dev": true, - "license": "MIT" - }, "node_modules/@nicolo-ribaudo/chokidar-2": { "version": "2.1.8-no-fsevents.3", "dev": true, @@ -3274,6 +3334,17 @@ "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", "dev": true }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.3.tgz", + "integrity": "sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" + } + }, "node_modules/@tanstack/match-sorter-utils": { "version": "8.8.4", "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", @@ -3345,40 +3416,6 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.10", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, "node_modules/@types/debug": { "version": "4.1.7", "license": "MIT", @@ -3390,6 +3427,7 @@ "version": "8.21.3", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -3399,6 +3437,7 @@ "version": "3.7.4", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -3407,28 +3446,8 @@ "node_modules/@types/estree": { "version": "0.0.51", "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "4.17.17", - "dev": true, "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.33", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } + "peer": true }, "node_modules/@types/hast": { "version": "2.3.4", @@ -3437,14 +3456,6 @@ "@types/unist": "*" } }, - "node_modules/@types/http-proxy": { - "version": "1.17.10", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -3502,11 +3513,6 @@ "@types/unist": "*" } }, - "node_modules/@types/mime": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ms": { "version": "0.7.31", "license": "MIT" @@ -3525,16 +3531,6 @@ "version": "15.7.5", "license": "MIT" }, - "node_modules/@types/qs": { - "version": "6.9.7", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "dev": true, - "license": "MIT" - }, "node_modules/@types/react": { "version": "18.0.31", "license": "MIT", @@ -3553,11 +3549,6 @@ "@types/react": "*" } }, - "node_modules/@types/retry": { - "version": "0.12.0", - "dev": true, - "license": "MIT" - }, "node_modules/@types/scheduler": { "version": "0.16.3", "license": "MIT" @@ -3567,31 +3558,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/serve-index": { - "version": "1.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.33", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -3606,14 +3572,6 @@ "version": "8.3.4", "license": "MIT" }, - "node_modules/@types/ws": { - "version": "8.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -3817,6 +3775,7 @@ "version": "1.11.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -3825,22 +3784,26 @@ "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.1", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.1", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -3850,12 +3813,14 @@ "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3867,6 +3832,7 @@ "version": "1.11.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -3875,6 +3841,7 @@ "version": "1.11.1", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -3882,12 +3849,14 @@ "node_modules/@webassemblyjs/utf8": { "version": "1.11.1", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3903,6 +3872,7 @@ "version": "1.11.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -3915,6 +3885,7 @@ "version": "1.11.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3926,6 +3897,7 @@ "version": "1.11.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -3939,61 +3911,23 @@ "version": "1.11.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" } }, - "node_modules/@webpack-cli/configtest": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@zattoo/use-double-click": { "version": "1.2.0", @@ -4007,18 +3941,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/accepts": { - "version": "1.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.8.2", "dev": true, @@ -4034,6 +3956,7 @@ "version": "1.8.0", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "acorn": "^8" } @@ -4105,17 +4028,6 @@ "ajv": "^6.9.1" } }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "dev": true, @@ -4182,11 +4094,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-flatten": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, "node_modules/array-includes": { "version": "3.1.6", "dev": true, @@ -4543,11 +4450,6 @@ "node": ">= 0.6.0" } }, - "node_modules/batch": { - "version": "0.6.1", - "dev": true, - "license": "MIT" - }, "node_modules/big.js": { "version": "5.2.2", "dev": true, @@ -4567,72 +4469,6 @@ "version": "5.2.1", "license": "MIT" }, - "node_modules/body-parser": { - "version": "1.20.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/bonjour-service": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, "node_modules/boolbase": { "version": "1.0.0", "dev": true, @@ -4750,14 +4586,6 @@ "version": "1.0.3", "license": "MIT" }, - "node_modules/bytes": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/call-bind": { "version": "1.0.2", "dev": true, @@ -4872,6 +4700,7 @@ "version": "1.0.3", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.0" } @@ -4933,19 +4762,6 @@ "version": "0.0.1", "license": "MIT" }, - "node_modules/clone-deep": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/clsx": { "version": "1.2.1", "license": "MIT", @@ -5003,52 +4819,6 @@ "dev": true, "license": "MIT" }, - "node_modules/compressible": { - "version": "2.0.18", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "license": "MIT" @@ -5058,56 +4828,16 @@ "dev": true, "license": "MIT" }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/consola": { "version": "2.15.3", "dev": true, "license": "MIT" }, - "node_modules/content-disposition": { - "version": "0.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/convert-source-map": { "version": "1.9.0", "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "0.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "dev": true, - "license": "MIT" - }, "node_modules/copy-anything": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.3.tgz", @@ -5148,11 +4878,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, "node_modules/cosmiconfig": { "version": "8.1.3", "dev": true, @@ -5434,25 +5159,6 @@ "dev": true, "license": "MIT" }, - "node_modules/default-gateway": { - "version": "6.0.3", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/define-properties": { "version": "1.2.0", "dev": true, @@ -5475,14 +5181,6 @@ "node": ">=0.4.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/dequal": { "version": "2.0.3", "license": "MIT", @@ -5498,20 +5196,6 @@ "minimalistic-assert": "^1.0.0" } }, - "node_modules/destroy": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, "node_modules/detect-node-es": { "version": "1.1.0", "license": "MIT" @@ -5564,22 +5248,6 @@ "version": "1.1.3", "license": "MIT" }, - "node_modules/dns-equal": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/dns-packet": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/doctrine": { "version": "3.0.0", "dev": true, @@ -5680,11 +5348,6 @@ "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", "integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==" }, - "node_modules/ee-first": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, "node_modules/ejs": { "version": "3.1.9", "dev": true, @@ -5729,14 +5392,6 @@ "node": ">= 4" } }, - "node_modules/encodeurl": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/enhanced-resolve": { "version": "5.12.0", "dev": true, @@ -5757,17 +5412,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/envinfo": { - "version": "7.8.1", - "dev": true, - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/error-ex": { "version": "1.3.2", "dev": true, @@ -5826,7 +5470,8 @@ "node_modules/es-module-lexer": { "version": "0.9.3", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/es-set-tostringtag": { "version": "2.0.1", @@ -5908,11 +5553,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -6420,23 +6060,11 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "dev": true, - "license": "MIT" - }, "node_modules/events": { "version": "3.3.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.x" } @@ -6449,28 +6077,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, "node_modules/expect": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", @@ -6491,65 +6097,6 @@ "version": "1.7.2", "license": "MIT" }, - "node_modules/express": { - "version": "4.18.2", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/extend": { "version": "3.0.2", "license": "MIT" @@ -6583,14 +6130,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, "node_modules/fastq": { "version": "1.15.0", "license": "ISC", @@ -6609,17 +6148,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "dev": true, @@ -6695,36 +6223,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/find-cache-dir": { "version": "3.3.2", "dev": true, @@ -6831,14 +6329,6 @@ "node": ">=0.4.x" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fraction.js": { "version": "4.2.0", "dev": true, @@ -6851,14 +6341,6 @@ "url": "https://www.patreon.com/infusion" } }, - "node_modules/fresh": { - "version": "0.5.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fs-extra": { "version": "10.1.0", "dev": true, @@ -6872,11 +6354,6 @@ "node": ">=12" } }, - "node_modules/fs-monkey": { - "version": "1.0.3", - "dev": true, - "license": "Unlicense" - }, "node_modules/fs-readdir-recursive": { "version": "1.1.0", "dev": true, @@ -6943,17 +6420,6 @@ "node": ">=6" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-symbol-description": { "version": "1.0.0", "dev": true, @@ -7001,7 +6467,8 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "dev": true, - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/globals": { "version": "11.12.0", @@ -7078,11 +6545,6 @@ "version": "1.0.2", "license": "MIT" }, - "node_modules/handle-thing": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, "node_modules/has": { "version": "1.0.3", "license": "MIT", @@ -7315,49 +6777,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/hpack.js": { - "version": "2.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-entities": { - "version": "2.3.3", - "dev": true, - "license": "MIT" - }, "node_modules/html-minifier-terser": { "version": "6.1.0", "dev": true, @@ -7406,86 +6825,6 @@ "node": ">=8.0.0" } }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "dev": true, - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "dev": true, @@ -7531,24 +6870,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-local": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "dev": true, @@ -7586,14 +6907,6 @@ "node": ">= 0.4" } }, - "node_modules/interpret": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/invariant": { "version": "2.2.4", "license": "MIT", @@ -7601,14 +6914,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ipaddr.js": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/is-array-buffer": { "version": "3.0.2", "dev": true, @@ -7719,20 +7024,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "license": "MIT", @@ -7800,17 +7091,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-regex": { "version": "1.1.4", "dev": true, @@ -7837,17 +7117,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-string": { "version": "1.0.7", "dev": true, @@ -7917,35 +7186,11 @@ "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "dev": true, "license": "ISC" }, - "node_modules/isobject": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jake": { "version": "10.8.5", "dev": true, @@ -8396,6 +7641,7 @@ "version": "27.5.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -8409,6 +7655,7 @@ "version": "4.0.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -8417,6 +7664,7 @@ "version": "8.1.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -8539,14 +7787,6 @@ "node": ">= 12" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/kleur": { "version": "4.1.5", "license": "MIT", @@ -8562,15 +7802,6 @@ "node": ">= 8" } }, - "node_modules/launch-editor": { - "version": "2.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" - } - }, "node_modules/levn": { "version": "0.4.1", "dev": true, @@ -8598,6 +7829,7 @@ "version": "4.3.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.11.5" } @@ -8958,26 +8190,11 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/memfs": { - "version": "3.4.13", - "dev": true, - "license": "Unlicense", - "dependencies": { - "fs-monkey": "^1.0.3" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, "node_modules/merge-stream": { "version": "2.0.0", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/merge2": { "version": "1.4.1", @@ -8986,14 +8203,6 @@ "node": ">= 8" } }, - "node_modules/methods": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micromark": { "version": "3.1.0", "funding": [ @@ -9567,17 +8776,6 @@ "version": "4.12.0", "license": "MIT" }, - "node_modules/mime": { - "version": "1.6.0", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.52.0", "license": "MIT", @@ -9595,12 +8793,20 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "bin": { + "mini-svg-data-uri": "cli.js" } }, "node_modules/minimalistic-assert": { @@ -9642,18 +8848,6 @@ "version": "2.1.2", "license": "MIT" }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "dev": true, - "license": "MIT", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, "node_modules/mz": { "version": "2.7.0", "license": "MIT", @@ -9684,18 +8878,11 @@ "dev": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "0.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/neo-async": { "version": "2.6.2", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/no-case": { "version": "3.0.4", @@ -9706,14 +8893,6 @@ "tslib": "^2.0.3" } }, - "node_modules/node-forge": { - "version": "1.3.1", - "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/node-html-parser": { "version": "5.4.2", "dev": true, @@ -9743,17 +8922,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nth-check": { "version": "2.1.1", "dev": true, @@ -9869,30 +9037,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/obuf": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "license": "ISC", @@ -9900,36 +9044,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.1", "dev": true, @@ -9974,18 +9088,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-retry": { - "version": "4.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/p-try": { "version": "2.2.0", "dev": true, @@ -10046,14 +9148,6 @@ "version": "6.0.1", "license": "MIT" }, - "node_modules/parseurl": { - "version": "1.3.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/pascal-case": { "version": "3.1.2", "dev": true, @@ -10099,11 +9193,6 @@ "version": "1.0.7", "license": "MIT" }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "dev": true, - "license": "MIT" - }, "node_modules/path-type": { "version": "4.0.0", "dev": true, @@ -11088,11 +10177,6 @@ "node": ">= 0.6.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, "node_modules/prop-types": { "version": "15.8.1", "license": "MIT", @@ -11114,26 +10198,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "license": "MIT" @@ -11162,20 +10226,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.11.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/querystring": { "version": "0.2.0", "engines": { @@ -11225,47 +10275,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rc-input-number": { "version": "7.4.2", "license": "MIT", @@ -11317,6 +10326,21 @@ "react": "^18.2.0" } }, + "node_modules/react-hook-form": { + "version": "7.43.9", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.43.9.tgz", + "integrity": "sha512-AUDN3Pz2NSeoxQ7Hs6OhQhDr6gtF9YRuutGDwPQqhSUAHJSgGl2VeY3qN19MG0SucpjgDiuMJ4iC5T5uB+eaNQ==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-is": { "version": "18.2.0", "license": "MIT" @@ -11530,17 +10554,6 @@ "node": ">=8.10.0" } }, - "node_modules/rechoir": { - "version": "0.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, "node_modules/recoil": { "version": "0.7.7", "license": "MIT", @@ -11781,11 +10794,6 @@ "node": ">=0.10.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.1", "license": "MIT", @@ -11801,25 +10809,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "dev": true, @@ -11828,14 +10817,6 @@ "node": ">=4" } }, - "node_modules/retry": { - "version": "0.13.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.0.4", "license": "MIT", @@ -12003,22 +10984,6 @@ "dev": true, "license": "MIT" }, - "node_modules/select-hose": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/selfsigned": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/semver": { "version": "6.3.0", "dev": true, @@ -12027,144 +10992,15 @@ "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "0.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, "node_modules/serialize-javascript": { "version": "6.0.1", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "randombytes": "^2.1.0" } }, - "node_modules/serve-index": { - "version": "1.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "dev": true, - "license": "ISC" - }, "node_modules/sha.js": { "version": "2.4.11", "license": "(MIT AND BSD-3-Clause)", @@ -12176,17 +11012,6 @@ "sha.js": "bin.js" } }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "dev": true, @@ -12206,14 +11031,6 @@ "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.8.0", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/side-channel": { "version": "1.0.4", "dev": true, @@ -12227,11 +11044,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" - }, "node_modules/slash": { "version": "2.0.0", "dev": true, @@ -12240,16 +11052,6 @@ "node": ">=6" } }, - "node_modules/sockjs": { - "version": "0.3.24", - "dev": true, - "license": "MIT", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, "node_modules/source-map": { "version": "0.6.1", "dev": true, @@ -12322,34 +11124,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/spdy": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -12371,14 +11145,6 @@ "node": ">=8" } }, - "node_modules/statuses": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "license": "MIT", @@ -12467,14 +11233,6 @@ "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "dev": true, @@ -12692,6 +11450,7 @@ "version": "5.3.7", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", @@ -12725,6 +11484,7 @@ "version": "3.1.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -12773,11 +11533,6 @@ "node": ">=0.8" } }, - "node_modules/thunky": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, "node_modules/to-fast-properties": { "version": "2.0.0", "dev": true, @@ -12800,14 +11555,6 @@ "version": "1.0.6", "license": "MIT" }, - "node_modules/toidentifier": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "license": "MIT", @@ -13033,27 +11780,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "1.6.18", - "dev": true, - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typed-array-length": { "version": "1.0.4", "dev": true, @@ -13244,14 +11970,6 @@ "node": ">= 10.0.0" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.10", "dev": true, @@ -13394,14 +12112,6 @@ "dev": true, "license": "ISC" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/utrie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", @@ -13441,14 +12151,6 @@ "node": ">=8" } }, - "node_modules/vary": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vfile": { "version": "5.3.7", "license": "MIT", @@ -13569,6 +12271,7 @@ "version": "2.4.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -13577,14 +12280,6 @@ "node": ">=10.13.0" } }, - "node_modules/wbuf": { - "version": "1.7.3", - "dev": true, - "license": "MIT", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, "node_modules/web-namespaces": { "version": "2.0.1", "license": "MIT", @@ -13597,6 +12292,7 @@ "version": "5.77.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -13639,154 +12335,11 @@ } } }, - "node_modules/webpack-cli": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.0.1", - "@webpack-cli/info": "^2.0.1", - "@webpack-cli/serve": "^2.0.1", - "colorette": "^2.0.14", - "commander": "^9.4.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "9.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.13.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/webpack-sources": { "version": "3.2.3", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" } @@ -13795,6 +12348,7 @@ "version": "3.1.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -13808,27 +12362,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/whatwg-mimetype": { "version": "2.3.0", "dev": true, @@ -13882,11 +12415,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wildcard": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/word-wrap": { "version": "1.2.3", "dev": true, @@ -13899,26 +12427,6 @@ "version": "1.0.2", "license": "ISC" }, - "node_modules/ws": { - "version": "8.13.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/yallist": { "version": "3.1.1", "dev": true, @@ -15220,10 +13728,6 @@ "dev": true, "requires": {} }, - "@discoveryjs/json-ext": { - "version": "0.5.7", - "dev": true - }, "@esbuild/win32-x64": { "version": "0.17.15", "optional": true @@ -15289,6 +13793,51 @@ "use-isomorphic-layout-effect": "^1.1.1" } }, + "@fortawesome/fontawesome-common-types": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", + "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", + "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fortawesome/free-brands-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz", + "integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fortawesome/free-regular-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz", + "integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz", + "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "requires": { + "prop-types": "^15.8.1" + } + }, "@headlessui/react": { "version": "1.7.13", "requires": { @@ -15442,10 +13991,6 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, - "@leichtgewicht/ip-codec": { - "version": "2.0.4", - "dev": true - }, "@nicolo-ribaudo/chokidar-2": { "version": "2.1.8-no-fsevents.3", "dev": true, @@ -15826,6 +14371,14 @@ "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", "dev": true }, + "@tailwindcss/forms": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.3.tgz", + "integrity": "sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==", + "requires": { + "mini-svg-data-uri": "^1.2.3" + } + }, "@tanstack/match-sorter-utils": { "version": "8.8.4", "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", @@ -15860,36 +14413,6 @@ "use-sync-external-store": "^1.2.0" } }, - "@types/body-parser": { - "version": "1.19.2", - "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/bonjour": { - "version": "3.5.10", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/connect-history-api-fallback": { - "version": "1.3.5", - "dev": true, - "requires": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, "@types/debug": { "version": "4.1.7", "requires": { @@ -15899,6 +14422,7 @@ "@types/eslint": { "version": "8.21.3", "dev": true, + "peer": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -15907,6 +14431,7 @@ "@types/eslint-scope": { "version": "3.7.4", "dev": true, + "peer": true, "requires": { "@types/eslint": "*", "@types/estree": "*" @@ -15914,26 +14439,8 @@ }, "@types/estree": { "version": "0.0.51", - "dev": true - }, - "@types/express": { - "version": "4.17.17", "dev": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.33", - "dev": true, - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } + "peer": true }, "@types/hast": { "version": "2.3.4", @@ -15941,13 +14448,6 @@ "@types/unist": "*" } }, - "@types/http-proxy": { - "version": "1.17.10", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -16002,10 +14502,6 @@ "@types/unist": "*" } }, - "@types/mime": { - "version": "3.0.1", - "dev": true - }, "@types/ms": { "version": "0.7.31" }, @@ -16021,14 +14517,6 @@ "@types/prop-types": { "version": "15.7.5" }, - "@types/qs": { - "version": "6.9.7", - "dev": true - }, - "@types/range-parser": { - "version": "1.2.4", - "dev": true - }, "@types/react": { "version": "18.0.31", "requires": { @@ -16046,10 +14534,6 @@ "@types/react": "*" } }, - "@types/retry": { - "version": "0.12.0", - "dev": true - }, "@types/scheduler": { "version": "0.16.3" }, @@ -16057,28 +14541,6 @@ "version": "7.3.13", "dev": true }, - "@types/serve-index": { - "version": "1.9.1", - "dev": true, - "requires": { - "@types/express": "*" - } - }, - "@types/serve-static": { - "version": "1.15.1", - "dev": true, - "requires": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "@types/sockjs": { - "version": "0.3.33", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -16091,13 +14553,6 @@ "@types/uuid": { "version": "8.3.4" }, - "@types/ws": { - "version": "8.5.4", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -16220,6 +14675,7 @@ "@webassemblyjs/ast": { "version": "1.11.1", "dev": true, + "peer": true, "requires": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -16227,19 +14683,23 @@ }, "@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", - "dev": true + "dev": true, + "peer": true }, "@webassemblyjs/helper-api-error": { "version": "1.11.1", - "dev": true + "dev": true, + "peer": true }, "@webassemblyjs/helper-buffer": { "version": "1.11.1", - "dev": true + "dev": true, + "peer": true }, "@webassemblyjs/helper-numbers": { "version": "1.11.1", "dev": true, + "peer": true, "requires": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -16248,11 +14708,13 @@ }, "@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", - "dev": true + "dev": true, + "peer": true }, "@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -16263,6 +14725,7 @@ "@webassemblyjs/ieee754": { "version": "1.11.1", "dev": true, + "peer": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -16270,17 +14733,20 @@ "@webassemblyjs/leb128": { "version": "1.11.1", "dev": true, + "peer": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { "version": "1.11.1", - "dev": true + "dev": true, + "peer": true }, "@webassemblyjs/wasm-edit": { "version": "1.11.1", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -16295,6 +14761,7 @@ "@webassemblyjs/wasm-gen": { "version": "1.11.1", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -16306,6 +14773,7 @@ "@webassemblyjs/wasm-opt": { "version": "1.11.1", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -16316,6 +14784,7 @@ "@webassemblyjs/wasm-parser": { "version": "1.11.1", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -16328,33 +14797,21 @@ "@webassemblyjs/wast-printer": { "version": "1.11.1", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" } }, - "@webpack-cli/configtest": { - "version": "2.0.1", - "dev": true, - "requires": {} - }, - "@webpack-cli/info": { - "version": "2.0.1", - "dev": true, - "requires": {} - }, - "@webpack-cli/serve": { - "version": "2.0.1", - "dev": true, - "requires": {} - }, "@xtuc/ieee754": { "version": "1.2.0", - "dev": true + "dev": true, + "peer": true }, "@xtuc/long": { "version": "4.2.2", - "dev": true + "dev": true, + "peer": true }, "@zattoo/use-double-click": { "version": "1.2.0", @@ -16366,14 +14823,6 @@ "version": "2.0.6", "dev": true }, - "accepts": { - "version": "1.3.8", - "dev": true, - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, "acorn": { "version": "8.8.2", "dev": true @@ -16381,6 +14830,7 @@ "acorn-import-assertions": { "version": "1.8.0", "dev": true, + "peer": true, "requires": {} }, "acorn-jsx": { @@ -16426,10 +14876,6 @@ "dev": true, "requires": {} }, - "ansi-html-community": { - "version": "0.0.8", - "dev": true - }, "ansi-regex": { "version": "5.0.1", "dev": true @@ -16474,10 +14920,6 @@ "is-array-buffer": "^3.0.1" } }, - "array-flatten": { - "version": "2.1.2", - "dev": true - }, "array-includes": { "version": "3.1.6", "dev": true, @@ -16734,10 +15176,6 @@ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" }, - "batch": { - "version": "0.6.1", - "dev": true - }, "big.js": { "version": "5.2.2", "dev": true @@ -16748,58 +15186,6 @@ "bn.js": { "version": "5.2.1" }, - "body-parser": { - "version": "1.20.1", - "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "bytes": { - "version": "3.1.2", - "dev": true - }, - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, - "bonjour-service": { - "version": "1.1.1", - "dev": true, - "requires": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, "boolbase": { "version": "1.0.0", "dev": true @@ -16886,10 +15272,6 @@ "buffer-xor": { "version": "1.0.3" }, - "bytes": { - "version": "3.0.0", - "dev": true - }, "call-bind": { "version": "1.0.2", "dev": true, @@ -16949,7 +15331,8 @@ }, "chrome-trace-event": { "version": "1.0.3", - "dev": true + "dev": true, + "peer": true }, "ci-info": { "version": "3.8.0", @@ -16981,15 +15364,6 @@ "client-only": { "version": "0.0.1" }, - "clone-deep": { - "version": "4.0.1", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, "clsx": { "version": "1.2.1" }, @@ -17028,43 +15402,6 @@ "version": "1.0.1", "dev": true }, - "compressible": { - "version": "2.0.18", - "dev": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - } - } - }, "concat-map": { "version": "0.0.1" }, @@ -17072,37 +15409,14 @@ "version": "1.0.11", "dev": true }, - "connect-history-api-fallback": { - "version": "2.0.0", - "dev": true - }, "consola": { "version": "2.15.3", "dev": true }, - "content-disposition": { - "version": "0.5.4", - "dev": true, - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "dev": true - }, "convert-source-map": { "version": "1.9.0", "dev": true }, - "cookie": { - "version": "0.5.0", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "dev": true - }, "copy-anything": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.3.tgz", @@ -17129,10 +15443,6 @@ "browserslist": "^4.21.5" } }, - "core-util-is": { - "version": "1.0.3", - "dev": true - }, "cosmiconfig": { "version": "8.1.3", "dev": true, @@ -17305,17 +15615,6 @@ "version": "0.1.4", "dev": true }, - "default-gateway": { - "version": "6.0.3", - "dev": true, - "requires": { - "execa": "^5.0.0" - } - }, - "define-lazy-prop": { - "version": "2.0.0", - "dev": true - }, "define-properties": { "version": "1.2.0", "dev": true, @@ -17327,10 +15626,6 @@ "delayed-stream": { "version": "1.0.0" }, - "depd": { - "version": "2.0.0", - "dev": true - }, "dequal": { "version": "2.0.3" }, @@ -17341,14 +15636,6 @@ "minimalistic-assert": "^1.0.0" } }, - "destroy": { - "version": "1.2.0", - "dev": true - }, - "detect-node": { - "version": "2.1.0", - "dev": true - }, "detect-node-es": { "version": "1.1.0" }, @@ -17387,17 +15674,6 @@ "dlv": { "version": "1.1.3" }, - "dns-equal": { - "version": "1.0.0", - "dev": true - }, - "dns-packet": { - "version": "5.5.0", - "dev": true, - "requires": { - "@leichtgewicht/ip-codec": "^2.0.1" - } - }, "doctrine": { "version": "3.0.0", "dev": true, @@ -17462,10 +15738,6 @@ "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", "integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==" }, - "ee-first": { - "version": "1.1.1", - "dev": true - }, "ejs": { "version": "3.1.9", "dev": true, @@ -17498,10 +15770,6 @@ "version": "3.0.0", "dev": true }, - "encodeurl": { - "version": "1.0.2", - "dev": true - }, "enhanced-resolve": { "version": "5.12.0", "dev": true, @@ -17514,10 +15782,6 @@ "version": "2.2.0", "dev": true }, - "envinfo": { - "version": "7.8.1", - "dev": true - }, "error-ex": { "version": "1.3.2", "dev": true, @@ -17567,7 +15831,8 @@ }, "es-module-lexer": { "version": "0.9.3", - "dev": true + "dev": true, + "peer": true }, "es-set-tostringtag": { "version": "2.0.1", @@ -17625,10 +15890,6 @@ "version": "3.1.1", "dev": true }, - "escape-html": { - "version": "1.0.3", - "dev": true - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -17967,17 +16228,10 @@ "version": "2.0.3", "dev": true }, - "etag": { - "version": "1.8.1", - "dev": true - }, - "eventemitter3": { - "version": "4.0.7", - "dev": true - }, "events": { "version": "3.3.0", - "dev": true + "dev": true, + "peer": true }, "evp_bytestokey": { "version": "1.0.3", @@ -17986,21 +16240,6 @@ "safe-buffer": "^5.1.1" } }, - "execa": { - "version": "5.1.1", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, "expect": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", @@ -18017,60 +16256,6 @@ "export-from-json": { "version": "1.7.2" }, - "express": { - "version": "4.18.2", - "dev": true, - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "array-flatten": { - "version": "1.1.1", - "dev": true - }, - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, "extend": { "version": "3.0.2" }, @@ -18096,10 +16281,6 @@ "version": "2.0.6", "dev": true }, - "fastest-levenshtein": { - "version": "1.0.16", - "dev": true - }, "fastq": { "version": "1.15.0", "requires": { @@ -18112,13 +16293,6 @@ "format": "^0.2.0" } }, - "faye-websocket": { - "version": "0.11.4", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, "file-entry-cache": { "version": "6.0.1", "dev": true, @@ -18170,32 +16344,6 @@ "to-regex-range": "^5.0.1" } }, - "finalhandler": { - "version": "1.2.0", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, "find-cache-dir": { "version": "3.3.2", "dev": true, @@ -18255,18 +16403,10 @@ "format": { "version": "0.2.2" }, - "forwarded": { - "version": "0.2.0", - "dev": true - }, "fraction.js": { "version": "4.2.0", "dev": true }, - "fresh": { - "version": "0.5.2", - "dev": true - }, "fs-extra": { "version": "10.1.0", "dev": true, @@ -18276,10 +16416,6 @@ "universalify": "^2.0.0" } }, - "fs-monkey": { - "version": "1.0.3", - "dev": true - }, "fs-readdir-recursive": { "version": "1.1.0", "dev": true @@ -18320,10 +16456,6 @@ "get-nonce": { "version": "1.0.1" }, - "get-stream": { - "version": "6.0.1", - "dev": true - }, "get-symbol-description": { "version": "1.0.0", "dev": true, @@ -18352,7 +16484,8 @@ }, "glob-to-regexp": { "version": "0.4.1", - "dev": true + "dev": true, + "peer": true }, "globals": { "version": "11.12.0", @@ -18403,10 +16536,6 @@ "hamt_plus": { "version": "1.0.2" }, - "handle-thing": { - "version": "2.0.1", - "dev": true - }, "has": { "version": "1.0.3", "requires": { @@ -18549,46 +16678,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hpack.js": { - "version": "2.1.6", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "html-entities": { - "version": "2.3.3", - "dev": true - }, "html-minifier-terser": { "version": "6.1.0", "dev": true, @@ -18620,55 +16709,6 @@ "text-segmentation": "^1.0.3" } }, - "http-deceiver": { - "version": "1.2.7", - "dev": true - }, - "http-errors": { - "version": "2.0.0", - "dev": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-parser-js": { - "version": "0.5.8", - "dev": true - }, - "http-proxy": { - "version": "1.18.1", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-middleware": { - "version": "2.0.6", - "dev": true, - "requires": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "dependencies": { - "is-plain-obj": { - "version": "3.0.0", - "dev": true - } - } - }, - "human-signals": { - "version": "2.1.0", - "dev": true - }, "iconv-lite": { "version": "0.6.3", "dev": true, @@ -18693,14 +16733,6 @@ "resolve-from": "^4.0.0" } }, - "import-local": { - "version": "3.1.0", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, "imurmurhash": { "version": "0.1.4", "dev": true @@ -18727,20 +16759,12 @@ "side-channel": "^1.0.4" } }, - "interpret": { - "version": "3.1.1", - "dev": true - }, "invariant": { "version": "2.2.4", "requires": { "loose-envify": "^1.0.0" } }, - "ipaddr.js": { - "version": "2.0.1", - "dev": true - }, "is-array-buffer": { "version": "3.0.2", "dev": true, @@ -18795,10 +16819,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-docker": { - "version": "2.2.1", - "dev": true - }, "is-extglob": { "version": "2.1.1" }, @@ -18829,13 +16849,6 @@ "is-plain-obj": { "version": "4.1.0" }, - "is-plain-object": { - "version": "2.0.4", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, "is-regex": { "version": "1.1.4", "dev": true, @@ -18851,10 +16864,6 @@ "call-bind": "^1.0.2" } }, - "is-stream": { - "version": "2.0.1", - "dev": true - }, "is-string": { "version": "1.0.7", "dev": true, @@ -18893,25 +16902,10 @@ "integrity": "sha512-yq8gMao5upkPoGEU9LsB2P+K3Kt8Q3fQFCGyNCWOAnJAMzEXVV9drYb0TXr42TTliLLhKIBvulgAXgtLLnwzGA==", "dev": true }, - "is-wsl": { - "version": "2.2.0", - "dev": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "dev": true - }, "isexe": { "version": "2.0.0", "dev": true }, - "isobject": { - "version": "3.0.1", - "dev": true - }, "jake": { "version": "10.8.5", "dev": true, @@ -19235,6 +17229,7 @@ "jest-worker": { "version": "27.5.1", "dev": true, + "peer": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -19243,11 +17238,13 @@ "dependencies": { "has-flag": { "version": "4.0.0", - "dev": true + "dev": true, + "peer": true }, "supports-color": { "version": "8.1.1", "dev": true, + "peer": true, "requires": { "has-flag": "^4.0.0" } @@ -19318,10 +17315,6 @@ } } }, - "kind-of": { - "version": "6.0.3", - "dev": true - }, "kleur": { "version": "4.1.5" }, @@ -19329,14 +17322,6 @@ "version": "2.0.6", "dev": true }, - "launch-editor": { - "version": "2.6.0", - "dev": true, - "requires": { - "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" - } - }, "levn": { "version": "0.4.1", "dev": true, @@ -19353,7 +17338,8 @@ }, "loader-runner": { "version": "4.3.0", - "dev": true + "dev": true, + "peer": true }, "loader-utils": { "version": "2.0.4", @@ -19587,28 +17573,14 @@ "@types/mdast": "^3.0.0" } }, - "memfs": { - "version": "3.4.13", - "dev": true, - "requires": { - "fs-monkey": "^1.0.3" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "dev": true - }, "merge-stream": { "version": "2.0.0", - "dev": true + "dev": true, + "peer": true }, "merge2": { "version": "1.4.1" }, - "methods": { - "version": "1.1.2", - "dev": true - }, "micromark": { "version": "3.1.0", "requires": { @@ -19894,10 +17866,6 @@ } } }, - "mime": { - "version": "1.6.0", - "dev": true - }, "mime-db": { "version": "1.52.0" }, @@ -19907,9 +17875,15 @@ "mime-db": "1.52.0" } }, - "mimic-fn": { - "version": "2.1.0", - "dev": true + "mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==" + }, + "mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==" }, "minimalistic-assert": { "version": "1.0.1" @@ -19936,14 +17910,6 @@ "ms": { "version": "2.1.2" }, - "multicast-dns": { - "version": "7.2.5", - "dev": true, - "requires": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - } - }, "mz": { "version": "2.7.0", "requires": { @@ -19959,13 +17925,10 @@ "version": "1.4.0", "dev": true }, - "negotiator": { - "version": "0.6.3", - "dev": true - }, "neo-async": { "version": "2.6.2", - "dev": true + "dev": true, + "peer": true }, "no-case": { "version": "3.0.4", @@ -19975,10 +17938,6 @@ "tslib": "^2.0.3" } }, - "node-forge": { - "version": "1.3.1", - "dev": true - }, "node-html-parser": { "version": "5.4.2", "dev": true, @@ -19998,13 +17957,6 @@ "version": "0.1.2", "dev": true }, - "npm-run-path": { - "version": "4.0.1", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, "nth-check": { "version": "2.1.1", "dev": true, @@ -20071,43 +18023,12 @@ "es-abstract": "^1.20.4" } }, - "obuf": { - "version": "1.1.2", - "dev": true - }, - "on-finished": { - "version": "2.4.1", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "dev": true - }, "once": { "version": "1.4.0", "requires": { "wrappy": "1" } }, - "onetime": { - "version": "5.1.2", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "8.4.2", - "dev": true, - "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - } - }, "optionator": { "version": "0.9.1", "dev": true, @@ -20134,14 +18055,6 @@ "p-limit": "^3.0.2" } }, - "p-retry": { - "version": "4.6.2", - "dev": true, - "requires": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - } - }, "p-try": { "version": "2.2.0", "dev": true @@ -20184,10 +18097,6 @@ "parse5": { "version": "6.0.1" }, - "parseurl": { - "version": "1.3.3", - "dev": true - }, "pascal-case": { "version": "3.1.2", "dev": true, @@ -20218,10 +18127,6 @@ "path-parse": { "version": "1.0.7" }, - "path-to-regexp": { - "version": "0.1.7", - "dev": true - }, "path-type": { "version": "4.0.0", "dev": true @@ -20679,10 +18584,6 @@ "version": "0.11.10", "dev": true }, - "process-nextick-args": { - "version": "2.0.1", - "dev": true - }, "prop-types": { "version": "15.8.1", "requires": { @@ -20699,20 +18600,6 @@ "property-information": { "version": "6.2.0" }, - "proxy-addr": { - "version": "2.0.7", - "dev": true, - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "dependencies": { - "ipaddr.js": { - "version": "1.9.1", - "dev": true - } - } - }, "proxy-from-env": { "version": "1.1.0" }, @@ -20736,13 +18623,6 @@ "version": "2.3.0", "dev": true }, - "qs": { - "version": "6.11.0", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, "querystring": { "version": "0.2.0" }, @@ -20765,33 +18645,6 @@ "safe-buffer": "^5.1.0" } }, - "range-parser": { - "version": "1.2.1", - "dev": true - }, - "raw-body": { - "version": "2.5.1", - "dev": true, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "dependencies": { - "bytes": { - "version": "3.1.2", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, "rc-input-number": { "version": "7.4.2", "requires": { @@ -20826,6 +18679,12 @@ "scheduler": "^0.23.0" } }, + "react-hook-form": { + "version": "7.43.9", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.43.9.tgz", + "integrity": "sha512-AUDN3Pz2NSeoxQ7Hs6OhQhDr6gtF9YRuutGDwPQqhSUAHJSgGl2VeY3qN19MG0SucpjgDiuMJ4iC5T5uB+eaNQ==", + "requires": {} + }, "react-is": { "version": "18.2.0" }, @@ -20940,13 +18799,6 @@ "picomatch": "^2.2.1" } }, - "rechoir": { - "version": "0.8.0", - "dev": true, - "requires": { - "resolve": "^1.20.0" - } - }, "recoil": { "version": "0.7.7", "requires": { @@ -21103,10 +18955,6 @@ "version": "2.0.2", "dev": true }, - "requires-port": { - "version": "1.0.0", - "dev": true - }, "resolve": { "version": "1.22.1", "requires": { @@ -21115,27 +18963,10 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, - "resolve-cwd": { - "version": "3.0.0", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "dev": true - } - } - }, "resolve-from": { "version": "4.0.0", "dev": true }, - "retry": { - "version": "0.13.1", - "dev": true - }, "reusify": { "version": "1.0.4" }, @@ -21226,132 +19057,18 @@ } } }, - "select-hose": { - "version": "2.0.0", - "dev": true - }, - "selfsigned": { - "version": "2.1.1", - "dev": true, - "requires": { - "node-forge": "^1" - } - }, "semver": { "version": "6.3.0", "dev": true }, - "send": { - "version": "0.18.0", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, - "ms": { - "version": "2.1.3", - "dev": true - } - } - }, "serialize-javascript": { "version": "6.0.1", "dev": true, + "peer": true, "requires": { "randombytes": "^2.1.0" } }, - "serve-index": { - "version": "1.9.1", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "dev": true - }, - "http-errors": { - "version": "1.6.3", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "dev": true - }, - "ms": { - "version": "2.0.0", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "dev": true - } - } - }, - "serve-static": { - "version": "1.15.0", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "dev": true - }, "sha.js": { "version": "2.4.11", "requires": { @@ -21359,13 +19076,6 @@ "safe-buffer": "^5.0.1" } }, - "shallow-clone": { - "version": "3.0.1", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, "shebang-command": { "version": "2.0.0", "dev": true, @@ -21377,10 +19087,6 @@ "version": "3.0.0", "dev": true }, - "shell-quote": { - "version": "1.8.0", - "dev": true - }, "side-channel": { "version": "1.0.4", "dev": true, @@ -21390,23 +19096,10 @@ "object-inspect": "^1.9.0" } }, - "signal-exit": { - "version": "3.0.7", - "dev": true - }, "slash": { "version": "2.0.0", "dev": true }, - "sockjs": { - "version": "0.3.24", - "dev": true, - "requires": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, "source-map": { "version": "0.6.1", "dev": true @@ -21448,29 +19141,6 @@ "space-separated-tokens": { "version": "2.0.2" }, - "spdy": { - "version": "4.0.2", - "dev": true, - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - } - }, - "spdy-transport": { - "version": "3.0.0", - "dev": true, - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -21488,10 +19158,6 @@ } } }, - "statuses": { - "version": "2.0.1", - "dev": true - }, "string_decoder": { "version": "1.3.0", "requires": { @@ -21553,10 +19219,6 @@ "dev": true, "peer": true }, - "strip-final-newline": { - "version": "2.0.0", - "dev": true - }, "strip-json-comments": { "version": "3.1.1", "dev": true @@ -21695,6 +19357,7 @@ "terser-webpack-plugin": { "version": "5.3.7", "dev": true, + "peer": true, "requires": { "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", @@ -21706,6 +19369,7 @@ "schema-utils": { "version": "3.1.1", "dev": true, + "peer": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -21738,10 +19402,6 @@ "thenify": ">= 3.1.0 < 4" } }, - "thunky": { - "version": "1.1.0", - "dev": true - }, "to-fast-properties": { "version": "2.0.0", "dev": true @@ -21755,10 +19415,6 @@ "toggle-selection": { "version": "1.0.6" }, - "toidentifier": { - "version": "1.0.1", - "dev": true - }, "trim-lines": { "version": "3.0.1" }, @@ -21902,22 +19558,6 @@ "version": "0.20.2", "dev": true }, - "type-is": { - "version": "1.6.18", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "dependencies": { - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true - } - } - }, "typed-array-length": { "version": "1.0.4", "dev": true, @@ -22027,10 +19667,6 @@ "version": "2.0.0", "dev": true }, - "unpipe": { - "version": "1.0.0", - "dev": true - }, "update-browserslist-db": { "version": "1.0.10", "dev": true, @@ -22105,10 +19741,6 @@ "util-deprecate": { "version": "1.0.2" }, - "utils-merge": { - "version": "1.0.1", - "dev": true - }, "utrie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", @@ -22136,10 +19768,6 @@ "sade": "^1.7.3" } }, - "vary": { - "version": "1.1.2", - "dev": true - }, "vfile": { "version": "5.3.7", "requires": { @@ -22201,24 +19829,19 @@ "watchpack": { "version": "2.4.0", "dev": true, + "peer": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, - "wbuf": { - "version": "1.7.3", - "dev": true, - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, "web-namespaces": { "version": "2.0.1" }, "webpack": { "version": "5.77.0", "dev": true, + "peer": true, "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -22249,6 +19872,7 @@ "schema-utils": { "version": "3.1.1", "dev": true, + "peer": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -22257,102 +19881,10 @@ } } }, - "webpack-cli": { - "version": "5.0.1", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.0.1", - "@webpack-cli/info": "^2.0.1", - "@webpack-cli/serve": "^2.0.1", - "colorette": "^2.0.14", - "commander": "^9.4.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "dependencies": { - "commander": { - "version": "9.5.0", - "dev": true - } - } - }, - "webpack-dev-middleware": { - "version": "5.3.3", - "dev": true, - "requires": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - } - }, - "webpack-dev-server": { - "version": "4.13.1", - "dev": true, - "requires": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" - } - }, - "webpack-merge": { - "version": "5.8.0", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, "webpack-sources": { "version": "3.2.3", - "dev": true - }, - "websocket-driver": { - "version": "0.7.4", "dev": true, - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "dev": true + "peer": true }, "whatwg-mimetype": { "version": "2.3.0", @@ -22388,10 +19920,6 @@ "is-typed-array": "^1.1.10" } }, - "wildcard": { - "version": "2.0.0", - "dev": true - }, "word-wrap": { "version": "1.2.3", "dev": true @@ -22399,11 +19927,6 @@ "wrappy": { "version": "1.0.2" }, - "ws": { - "version": "8.13.0", - "dev": true, - "requires": {} - }, "yallist": { "version": "3.1.1", "dev": true diff --git a/client/package.json b/client/package.json index 09b83341f..c45450b95 100644 --- a/client/package.json +++ b/client/package.json @@ -6,8 +6,7 @@ "scripts": { "build": "vite build", "dev": "vite", - "preview-prod": "vite preview", - "build-dev": "Webpack . --watch" + "preview-prod": "vite preview" }, "repository": { "type": "git", @@ -21,6 +20,11 @@ }, "homepage": "https://github.com/danny-avila/chatgpt-clone#readme", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-brands-svg-icons": "^6.4.0", + "@fortawesome/free-regular-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@fortawesome/react-fontawesome": "^0.2.0", "@headlessui/react": "^1.7.13", "@radix-ui/react-alert-dialog": "^1.0.2", "@radix-ui/react-checkbox": "^1.0.3", @@ -30,6 +34,7 @@ "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-slider": "^1.1.1", "@radix-ui/react-tabs": "^1.0.3", + "@tailwindcss/forms": "^0.5.3", "@tanstack/react-query": "^4.28.0", "@types/jest": "^29.5.0", "@types/node": "^18.15.10", @@ -51,6 +56,7 @@ "rc-input-number": "^7.4.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.43.9", "react-lazy-load": "^4.0.1", "react-markdown": "^8.0.6", "react-router-dom": "^6.9.0", @@ -108,9 +114,6 @@ "ts-loader": "^9.4.2", "typescript": "^4.9.5", "vite": "^4.2.1", - "vite-plugin-html": "^3.2.0", - "webpack": "^5.77.0", - "webpack-cli": "^5.0.1", - "webpack-dev-server": "^4.11.1" + "vite-plugin-html": "^3.2.0" } } diff --git a/client/src/App.jsx b/client/src/App.jsx index 9fe0fe22c..87df5c025 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,94 +1,93 @@ -import { useEffect } from 'react'; -import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom'; +import { createBrowserRouter, RouterProvider, Navigate, Outlet } from 'react-router-dom'; import Root from './routes/Root'; import Chat from './routes/Chat'; import Search from './routes/Search'; -import store from './store'; -import { useRecoilState, useSetRecoilState } from 'recoil'; import { ScreenshotProvider } from './utils/screenshotContext.jsx'; -import { useGetSearchEnabledQuery, useGetUserQuery, useGetEndpointsQuery, useGetPresetsQuery} from '~/data-provider'; -import {ReactQueryDevtools} from '@tanstack/react-query-devtools'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { Login, Registration, RequestPasswordReset, ResetPassword } from './components/Auth'; +import { AuthContextProvider } from './hooks/AuthContext'; +import { RecoilRoot } from 'recoil'; +import { QueryClient, QueryClientProvider, QueryCache } from '@tanstack/react-query'; +import { ThemeProvider } from './hooks/ThemeContext'; +import { useApiErrorBoundary } from './hooks/ApiErrorBoundaryContext'; +import ApiErrorWatcher from './components/Auth/ApiErrorWatcher'; +const AuthLayout = () => ( + + + + +); const router = createBrowserRouter([ { - path: '/', - element: , + path: 'register', + element: + }, + { + path: 'forgot-password', + element: + }, + { + path: 'reset-password', + element: + }, + { + element: , children: [ { - index: true, - element: ( - - ) + path: 'login', + element: }, { - path: 'chat/:conversationId?', - element: - }, - { - path: 'search/:query?', - element: + path: '/', + element: , + children: [ + { + index: true, + element: ( + + ) + }, + { + path: 'chat/:conversationId?', + element: + }, + { + path: 'search/:query?', + element: + } + ] } ] } ]); const App = () => { - const [user, setUser] = useRecoilState(store.user); - const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled); - const setEndpointsConfig = useSetRecoilState(store.endpointsConfig); - const setPresets = useSetRecoilState(store.presets); + const { setError } = useApiErrorBoundary(); - const searchEnabledQuery = useGetSearchEnabledQuery(); - const userQuery = useGetUserQuery(); - const endpointsQuery = useGetEndpointsQuery(); - const presetsQuery = useGetPresetsQuery(); + const queryClient = new QueryClient({ + queryCache: new QueryCache({ + onError: error => { + if (error?.response?.status === 401) { + setError(error); + } + } + }) + }); - useEffect(() => { - if(endpointsQuery.data) { - setEndpointsConfig(endpointsQuery.data); - } else if(endpointsQuery.isError) { - console.error("Failed to get endpoints", endpointsQuery.error); - window.location.href = '/auth/login'; - } - }, [endpointsQuery.data, endpointsQuery.isError]); - - useEffect(() => { - if(presetsQuery.data) { - setPresets(presetsQuery.data); - } else if(presetsQuery.isError) { - console.error("Failed to get presets", presetsQuery.error); - window.location.href = '/auth/login'; - } - }, [presetsQuery.data, presetsQuery.isError]); - - useEffect(() => { - if (searchEnabledQuery.data) { - setIsSearchEnabled(searchEnabledQuery.data); - } else if(searchEnabledQuery.isError) { - console.error("Failed to get search enabled", searchEnabledQuery.error); - } - }, [searchEnabledQuery.data, searchEnabledQuery.isError]); - - useEffect(() => { - if (userQuery.data) { - setUser(userQuery.data); - } else if(userQuery.isError) { - console.error("Failed to get user", userQuery.error); - window.location.href = '/auth/login'; - } - }, [userQuery.data, userQuery.isError]); - - if (user) - return ( - <> - - - - ); - else return
; + return ( + + + + + + + + + ); }; export default () => ( diff --git a/client/src/components/Auth/ApiErrorWatcher.tsx b/client/src/components/Auth/ApiErrorWatcher.tsx new file mode 100644 index 000000000..09827065a --- /dev/null +++ b/client/src/components/Auth/ApiErrorWatcher.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { useApiErrorBoundary } from '~/hooks/ApiErrorBoundaryContext'; +import { useNavigate } from 'react-router-dom'; + +const ApiErrorWatcher = () => { + const { error } = useApiErrorBoundary(); + const navigate = useNavigate(); + React.useEffect(() => { + if (error?.response?.status === 500) { + // do something with error + // navigate('/login'); + } + }, [error, navigate]); + + return null; +}; + +export default ApiErrorWatcher; diff --git a/client/src/components/Auth/Login.tsx b/client/src/components/Auth/Login.tsx new file mode 100644 index 000000000..e157725ad --- /dev/null +++ b/client/src/components/Auth/Login.tsx @@ -0,0 +1,184 @@ +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { TLoginUser } from "~/data-provider"; +import { useAuthContext } from "~/hooks/AuthContext"; +import { useNavigate } from "react-router-dom"; + +function Login() { + const { login, error, isAuthenticated } = useAuthContext(); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + + const navigate = useNavigate(); + + useEffect(() => { + if (isAuthenticated) { + navigate("/chat/new"); + } + }, [isAuthenticated, navigate]) + + + const SERVER_URL = import.meta.env.DEV + ? import.meta.env.VITE_SERVER_URL_DEV + : import.meta.env.VITE_SERVER_URL_PROD; + const showGoogleLogin = + import.meta.env.VITE_SHOW_GOOGLE_LOGIN_OPTION === "true"; + + return ( +
+
+

Welcome back

+ {error && ( +
+ Unable to login with the information provided. Please check your + credentials and try again. +
+ )} +
login(data))} + > +
+
+ + +
+ {errors.email && ( + + {/* @ts-ignore */} + {errors.email.message} + + )} +
+
+
+ + +
+ + {errors.password && ( + + {/* @ts-ignore */} + {errors.password.message} + + )} +
+ + Forgot Password? + +
+ +
+
+

+ {" "} + Don't have an account?{" "} + + Sign up + +

+ {showGoogleLogin && ( + <> +
+
Or
+
+ + + )} +
+
+ ); +} + +export default Login; diff --git a/client/src/components/Auth/Registration.tsx b/client/src/components/Auth/Registration.tsx new file mode 100644 index 000000000..5209959fb --- /dev/null +++ b/client/src/components/Auth/Registration.tsx @@ -0,0 +1,315 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useForm } from "react-hook-form"; +import { useRegisterUserMutation, TRegisterUser } from "~/data-provider"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFacebook } from "@fortawesome/free-brands-svg-icons"; +import { faGoogle } from "@fortawesome/free-brands-svg-icons"; + +function Registration() { + const SERVER_URL = import.meta.env.DEV + ? import.meta.env.VITE_SERVER_URL_DEV + : import.meta.env.VITE_SERVER_URL_PROD; + const showGoogleLogin = + import.meta.env.VITE_SHOW_GOOGLE_LOGIN_OPTION === "true"; + + const navigate = useNavigate(); + const { + register, + watch, + handleSubmit, + formState: { errors }, + } = useForm({ mode: "onChange" }); + const [error, setError] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const registerUser = useRegisterUserMutation(); + + const password = watch("password"); + + const onRegisterUserFormSubmit = (data: TRegisterUser) => { + registerUser.mutate(data, { + onSuccess: () => { + navigate("/chat/new"); + }, + onError: (error) => { + setError(true); + if (error.response?.data?.message) { + setErrorMessage(error.response?.data?.message); + } + }, + }); + }; + + return ( +
+
+

+ Create your account +

+ {error && ( +
+ There was an error attempting to register your account. Please try + again. {errorMessage} +
+ )} +
onRegisterUserFormSubmit(data))} + > +
+
+ { + e.preventDefault(); + return false; + }} + {...register("name", { + required: "Name is required", + minLength: { + value: 3, + message: "Name must be at least 3 characters", + }, + maxLength: { + value: 80, + message: "Name must be less than 80 characters", + }, + })} + aria-invalid={!!errors.name} + className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" + placeholder=" " + > + +
+ + {errors.name && ( + + {/* @ts-ignore */} + {errors.name.message} + + )} +
+
+
+ + +
+ + {errors.username && ( + + {/* @ts-ignore */} + {errors.username.message} + + )} +
+
+
+ + +
+ {errors.email && ( + + {/* @ts-ignore */} + {errors.email.message} + + )} +
+
+
+ + +
+ + {errors.password && ( + + {/* @ts-ignore */} + {errors.password.message} + + )} +
+
+
+ { + e.preventDefault(); + return false; + }} + {...register("confirm_password", { + validate: (value) => + value === password || "Passwords do not match", + })} + aria-invalid={!!errors.confirm_password} + className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" + placeholder=" " + > + +
+ + {errors.confirm_password && ( + + {/* @ts-ignore */} + {errors.confirm_password.message} + + )} +
+
+ +
+
+

+ {" "} + Already have an account?{" "} + + Login + +

+ {showGoogleLogin && ( + <> +
+
Or
+
+ +
+ + +

Login with Google

+
+ {/* */} +
+ + )} +
+
+ ); +} + +export default Registration; diff --git a/client/src/components/Auth/RequestPasswordReset.tsx b/client/src/components/Auth/RequestPasswordReset.tsx new file mode 100644 index 000000000..198065de0 --- /dev/null +++ b/client/src/components/Auth/RequestPasswordReset.tsx @@ -0,0 +1,115 @@ +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { useRequestPasswordResetMutation, TRequestPasswordReset } from "~/data-provider"; + +function RequestPasswordReset() { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + const requestPasswordReset = useRequestPasswordResetMutation(); + const [success, setSuccess] = useState(false); + const [requestError, setRequestError] = useState(false); + const [resetLink, setResetLink] = useState(""); + + const onSubmit = (data: TRequestPasswordReset) => { + requestPasswordReset.mutate(data, { + onSuccess: (data) => { + setSuccess(true); + setResetLink(data.link); + }, + onError: () => { + setRequestError(true); + setTimeout(() => { + setRequestError(false); + }, 5000); + } + }); + }; + + return ( +
+
+

+ Reset your password +

+ {success && ( +
+ Click HERE to reset your password. + {/* An email has been sent with instructions on how to reset your password. */} +
+ )} + {requestError && ( +
+ There was a problem resetting your password. There was no user found with the email address provided. Please try again. +
+ )} +
+
+
+ + +
+ {errors.email && ( + + {/* @ts-ignore */} + {errors.email.message} + + )} +
+
+ +
+
+
+
+ ); +} + +export default RequestPasswordReset; \ No newline at end of file diff --git a/client/src/components/Auth/ResetPassword.tsx b/client/src/components/Auth/ResetPassword.tsx new file mode 100644 index 000000000..d983d383e --- /dev/null +++ b/client/src/components/Auth/ResetPassword.tsx @@ -0,0 +1,176 @@ +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import {useResetPasswordMutation, TResetPassword} from "~/data-provider"; +import { useNavigate, useSearchParams } from "react-router-dom"; + +function ResetPassword() { + const { + register, + handleSubmit, + watch, + formState: { errors }, + } = useForm(); + const resetPassword = useResetPasswordMutation(); + const [resetError, setResetError] = useState(false); + const [params] = useSearchParams(); + const navigate = useNavigate(); + const password = watch("password"); + + const onSubmit = (data: TResetPassword) => { + resetPassword.mutate(data, { + onError: () => { + setResetError(true); + } + }); + }; + + if (resetPassword.isSuccess) { + return ( +
+
+

+ Password Reset Success +

+
+ You may now login with your new password. +
+ +
+
+ ) + } + else { + return ( +
+
+

+ Reset your password +

+ {resetError && ( +
+ This password reset token is no longer valid. Click here to try again. +
+ )} +
+
+
+ + + + +
+ + {errors.password && ( + + {/* @ts-ignore */} + {errors.password.message} + + )} +
+
+
+ { + e.preventDefault(); + return false; + }} + {...register("confirm_password", { + validate: (value) => + value === password || "Passwords do not match", + })} + aria-invalid={!!errors.confirm_password} + className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" + placeholder=" " + > + +
+ {errors.confirm_password && ( + + {/* @ts-ignore */} + {errors.confirm_password.message} + + )} + {errors.token && ( + + {/* @ts-ignore */} + {errors.token.message} + + )} + {errors.userId && ( + + {/* @ts-ignore */} + {errors.userId.message} + + )} +
+
+ +
+
+
+
+ ) + } +}; + +export default ResetPassword; \ No newline at end of file diff --git a/client/src/components/Auth/index.ts b/client/src/components/Auth/index.ts new file mode 100644 index 000000000..89b8899e8 --- /dev/null +++ b/client/src/components/Auth/index.ts @@ -0,0 +1,4 @@ +export { default as Login } from './Login'; +export { default as Registration } from './Registration'; +export { default as RequestPasswordReset } from './RequestPasswordReset'; +export { default as ResetPassword } from './ResetPassword'; \ No newline at end of file diff --git a/client/src/components/MessageHandler/index.jsx b/client/src/components/MessageHandler/index.jsx index b51426503..6bea4743c 100644 --- a/client/src/components/MessageHandler/index.jsx +++ b/client/src/components/MessageHandler/index.jsx @@ -4,6 +4,7 @@ import { SSE } from '~/data-provider/sse.mjs'; import createPayload from '~/data-provider/createPayload'; import { useAbortRequestWithMessage } from '~/data-provider'; import store from '~/store'; +import { useAuthContext } from '~/hooks/AuthContext'; export default function MessageHandler() { const submission = useRecoilValue(store.submission); @@ -11,6 +12,7 @@ export default function MessageHandler() { const setMessages = useSetRecoilState(store.messages); const setConversation = useSetRecoilState(store.conversation); const resetLatestMessage = useResetRecoilState(store.latestMessage); + const { token } = useAuthContext(); const { refreshConversations } = store.useConversations(); @@ -158,7 +160,8 @@ export default function MessageHandler() { fetch(`/api/ask/${endpoint}/abort`, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ abortKey: conversationId @@ -187,7 +190,7 @@ export default function MessageHandler() { const events = new SSE(server, { payload: JSON.stringify(payload), - headers: { 'Content-Type': 'application/json' } + headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`} }); events.onmessage = e => { diff --git a/client/src/components/Nav/Logout.jsx b/client/src/components/Nav/Logout.jsx index f68a768c0..d1948dbca 100644 --- a/client/src/components/Nav/Logout.jsx +++ b/client/src/components/Nav/Logout.jsx @@ -1,22 +1,22 @@ import React from 'react'; import LogOutIcon from '../svg/LogOutIcon'; -import { useRecoilValue } from 'recoil'; -import store from '~/store'; +import { useAuthContext } from '~/hooks/AuthContext'; export default function Logout() { - const user = useRecoilValue(store.user); + const { user, logout } = useAuthContext(); - const clickHandler = () => { - window.location.href = '/auth/logout'; + const handleLogout = () => { + logout() + window.location.reload(); }; return ( ); diff --git a/client/src/components/Nav/index.jsx b/client/src/components/Nav/index.jsx index b63cf11e7..320b15fc4 100644 --- a/client/src/components/Nav/index.jsx +++ b/client/src/components/Nav/index.jsx @@ -8,10 +8,11 @@ import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useGetConversationsQuery, useSearchQuery } from '~/data-provider'; import useDebounce from '~/hooks/useDebounce'; import store from '~/store'; +import { useAuthContext } from '~/hooks/AuthContext'; export default function Nav({ navVisible, setNavVisible }) { const [isHovering, setIsHovering] = useState(false); - + const { isAuthenticated } = useAuthContext(); const containerRef = useRef(null); const scrollPositionRef = useRef(null); @@ -22,7 +23,7 @@ export default function Nav({ navVisible, setNavVisible }) { const [pages, setPages] = useState(1); // data provider - const getConversationsQuery = useGetConversationsQuery(pageNumber); + const getConversationsQuery = useGetConversationsQuery(pageNumber, { enabled: isAuthenticated }); // search const searchQuery = useRecoilValue(store.searchQuery); diff --git a/client/src/data-provider/api-endpoints.ts b/client/src/data-provider/api-endpoints.ts index 2ae4fc55f..52843141e 100644 --- a/client/src/data-provider/api-endpoints.ts +++ b/client/src/data-provider/api-endpoints.ts @@ -1,5 +1,5 @@ export const user = () => { - return `/api/me`; + return `/api/auth/user`; }; export const messages = (id: string) => { @@ -49,3 +49,35 @@ export const aiEndpoints = () => { export const tokenizer = () => { return `/api/tokenizer`; } + +export const login = () => { + return '/api/auth/login'; +} + +export const logout = () => { + return '/api/auth/logout'; +} + +export const register = () => { + return '/api/auth/register'; +} + +export const loginFacebook = () => { + return '/api/auth/facebook'; +} + +export const loginGoogle = () => { + return '/api/auth/google'; +} + +export const refreshToken = () => { + return '/api/auth/refresh'; +} + +export const requestPasswordReset = () => { + return '/api/auth/requestPasswordReset'; +} + +export const resetPassword = () => { + return '/api/auth/resetPassword'; +} \ No newline at end of file diff --git a/client/src/data-provider/data-service.ts b/client/src/data-provider/data-service.ts index f4d532e7b..ba5359297 100644 --- a/client/src/data-provider/data-service.ts +++ b/client/src/data-provider/data-service.ts @@ -67,4 +67,32 @@ export const getAIEndpoints = () => { export const updateTokenCount = (text: string) => { return request.post(endpoints.tokenizer(), {arg: text}); +} + +export const login = (payload: t.TLoginUser) => { + return request.post(endpoints.login(), payload); +} + +export const logout = () => { + return request.post(endpoints.logout()); +} + +export const register = (payload: t.TRegisterUser) => { + return request.post(endpoints.register(), payload); +} + +export const refreshToken = () => { + return request.post(endpoints.refreshToken()); +} + +export const getLoginGoogle = () => { + return request.get(endpoints.loginGoogle()); +} + +export const requestPasswordReset = (payload: t.TRequestPasswordReset) => { + return request.post(endpoints.requestPasswordReset(), payload); +} + +export const resetPassword = (payload: t.TResetPassword) => { + return request.post(endpoints.resetPassword(), payload); } \ No newline at end of file diff --git a/client/src/data-provider/react-query-service.ts b/client/src/data-provider/react-query-service.ts index 0755ea505..1d585ef4a 100644 --- a/client/src/data-provider/react-query-service.ts +++ b/client/src/data-provider/react-query-service.ts @@ -8,6 +8,7 @@ import { } from "@tanstack/react-query"; import * as t from "./types"; import * as dataService from "./data-service"; +import axios from 'axios'; export enum QueryKeys { messages = "messsages", @@ -25,11 +26,13 @@ export const useAbortRequestWithMessage = (): UseMutationResult dataService.abortRequestWithMessage(endpoint, abortKey, message)); }; -export const useGetUserQuery = (): QueryObserverResult => { +export const useGetUserQuery = (config?: UseQueryOptions): QueryObserverResult => { return useQuery([QueryKeys.user], () => dataService.getUser(), { refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, + retry: false, + ...config, }); }; @@ -120,11 +123,13 @@ export const useClearConversationsMutation = (): UseMutationResult => { }); }; -export const useGetConversationsQuery = (pageNumber: string): QueryObserverResult => { - return useQuery([QueryKeys.allConversations, pageNumber], () => +export const useGetConversationsQuery = (pageNumber: string, config?: UseQueryOptions): QueryObserverResult => { + return useQuery([QueryKeys.allConversations, pageNumber], () => dataService.getConversations(pageNumber), { refetchOnReconnect: false, refetchOnMount: false, + retry: 1, + ...config, } ); } @@ -176,11 +181,12 @@ export const useUpdatePresetMutation = (): UseMutationResult => { - return useQuery([QueryKeys.presets], () => dataService.getPresets(), { +export const useGetPresetsQuery = (config?: UseQueryOptions): QueryObserverResult => { + return useQuery([QueryKeys.presets], () => dataService.getPresets(), { refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, + ...config, }); }; @@ -223,4 +229,52 @@ export const useUpdateTokenCountMutation = (): UseMutationResult => { + const queryClient = useQueryClient(); + return useMutation( + (payload: t.TLoginUserRequest) => + dataService.login(payload), + { + onSuccess: () => { + queryClient.invalidateQueries([QueryKeys.user]); + }, + } + ); +} + +export const useRegisterUserMutation = (): UseMutationResult => { + const queryClient = useQueryClient(); + return useMutation( + (payload: t.TRegisterUser) => + dataService.register(payload), + { + onSuccess: () => { + queryClient.invalidateQueries([QueryKeys.user]); + }, + } + ); +} + +export const useLogoutUserMutation = (): UseMutationResult => { + const queryClient = useQueryClient(); + return useMutation(() => dataService.logout(), { + onSuccess: () => { + queryClient.invalidateQueries([QueryKeys.user]); + }, + }); +} + +export const useRefreshTokenMutation = (): UseMutationResult => { + return useMutation(() => dataService.refreshToken(), { + }); +} + +export const useRequestPasswordResetMutation = (): UseMutationResult => { + return useMutation((payload: t.TRequestPasswordReset) => dataService.requestPasswordReset(payload)); +} + +export const useResetPasswordMutation = (): UseMutationResult => { + return useMutation((payload: t.TResetPassword) => dataService.resetPassword(payload)); } \ No newline at end of file diff --git a/client/src/data-provider/request.ts b/client/src/data-provider/request.ts index d79562ab3..8f146e5ea 100644 --- a/client/src/data-provider/request.ts +++ b/client/src/data-provider/request.ts @@ -1,7 +1,7 @@ import axios, { AxiosRequestConfig } from "axios"; async function _get(url: string, options?: AxiosRequestConfig): Promise { - const response = await axios.get(url, { withCredentials: true, ...options}); + const response = await axios.get(url, { ...options}); return response.data; } diff --git a/client/src/data-provider/types.ts b/client/src/data-provider/types.ts index b3206fce6..f06de9a9d 100644 --- a/client/src/data-provider/types.ts +++ b/client/src/data-provider/types.ts @@ -98,8 +98,14 @@ export type TPreset = { } export type TUser = { + id: string, username: string, - display: string + email: string, + name: string, + avatar: string, + role: string, + createdAt: string, + updatedAt: string, }; export type TGetConversationsResponse = { @@ -160,4 +166,31 @@ export type TMessageTreeNode = {} export type TSearchMessage = {} -export type TSearchMessageTreeNode = {} \ No newline at end of file +export type TSearchMessageTreeNode = {} + +export type TRegisterUser = { + name: string, + email: string, + username: string, + password: string, +} + +export type TLoginUser = { + email: string, + password: string, +} + +export type TLoginResponse = { + token: string, + user: TUser +} + +export type TRequestPasswordReset = { + email: string, +} + +export type TResetPassword = { + userId: string, + token: string, + password: string, +} \ No newline at end of file diff --git a/client/src/hooks/ApiErrorBoundaryContext.tsx b/client/src/hooks/ApiErrorBoundaryContext.tsx new file mode 100644 index 000000000..8a64fbe3a --- /dev/null +++ b/client/src/hooks/ApiErrorBoundaryContext.tsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; + +export type ApiError = { + error: any, + setError: (error: any) => void +}; + +const ApiErrorBoundaryContext = React.createContext(undefined); + +export const ApiErrorBoundaryProvider = ({ + value, + children +}: { + value?: ApiError, + children: React.ReactNode +}) => { + const [error, setError] = useState(false); + return ( + + {children} + + ); +}; + +export const useApiErrorBoundary = () => { + const context = React.useContext(ApiErrorBoundaryContext); + + if (context === undefined) { + throw new Error('useApiErrorBoundary must be used inside ApiErrorBoundaryProvider'); + } + + return context; +}; diff --git a/client/src/hooks/AuthContext.tsx b/client/src/hooks/AuthContext.tsx new file mode 100644 index 000000000..bf0dfbee7 --- /dev/null +++ b/client/src/hooks/AuthContext.tsx @@ -0,0 +1,175 @@ +import { useState, useCallback, useEffect, useMemo, ReactNode, createContext, useContext } from 'react'; +import { + TUser, + TLoginResponse, + setTokenHeader, + useLoginUserMutation, + useLogoutUserMutation, + useGetUserQuery, + useRefreshTokenMutation, + TLoginUser +} from '~/data-provider'; +import { useNavigate, useLocation } from 'react-router-dom'; +import store from '~/store'; + +export type TAuthContext = { + user: TUser | undefined, + token: string | undefined, + isAuthenticated: boolean, + isLoading: boolean, + error: string | undefined, + login: (data: TLoginUser) => void, + logout: () => void +}; + +export type TUserContext = { + user?: TUser | undefined, + token: string | undefined, + isAuthenticated: boolean, + redirect?: string +}; + +const AuthContext = createContext (undefined); + +const AuthContextProvider = ({ children }: { children: ReactNode }) => { + const [user, setUser] = useState(undefined); + const [token, setToken] = useState (undefined); + const [error, setError] = useState (undefined); + const [isLoading, setIsLoading] = useState(false); + const [isAuthenticated, setIsAuthenticated] = useState(false); + + const navigate = useNavigate(); + + const loginUser = useLoginUserMutation(); + const logoutUser = useLogoutUserMutation(); + const userQuery = useGetUserQuery({ enabled: !!token }); + const refreshToken = useRefreshTokenMutation(); + + const location = useLocation(); + + const { newConversation } = store.useConversation(); + + const setUserContext = (userContext: TUserContext) => { + const { token, isAuthenticated, user, redirect } = userContext; + if(user) { + setUser(user); + } + setToken(token); + setTokenHeader(token); + setIsAuthenticated(isAuthenticated); + if (redirect) { + navigate(redirect); + } + }; + + const getCookieValue = key => { + let keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)'); + return keyValue ? keyValue[2] : null; + }; + + const login = (data: TLoginUser) => { + loginUser.mutate(data, { + onSuccess: (data: TLoginResponse) => { + const { user, token } = data; + setUserContext({ token, isAuthenticated: true, user, redirect: '/chat/new' }); + }, + onError: error => { + setError(error.message); + }, + }); + }; + + const logout = () => { + document.cookie.split(';').forEach(c => { + document.cookie = c + .replace(/^ +/, '') + .replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/'); + }); + logoutUser.mutate(undefined, { + onSuccess: () => { + setUserContext({ token: undefined, isAuthenticated: false, user: undefined, redirect: '/login' }); + }, + onError: error => { + setError(error.message); + } + }); + }; + + useEffect(() => { + if (userQuery.data) { + setUser(userQuery.data); + } + else if (userQuery.isError) { + setError(userQuery.error.message); + navigate('/login'); + } + if (error && isAuthenticated) { + setError(undefined); + } + if (!token || !isAuthenticated) { + const tokenFromCookie = getCookieValue('token'); + if (tokenFromCookie) { + // debugger; + setUserContext({ token: tokenFromCookie, isAuthenticated: true, user: userQuery.data, redirect: '/chat/new' }) + } + else { + navigate('/login'); + } + } + }, [token, isAuthenticated, userQuery.data, userQuery.isError]); + + // const silentRefresh = useCallback(() => { + // refreshToken.mutate(undefined, { + // onSuccess: (data: TLoginResponse) => { + // const { user, token } = data; + // setUserContext({ token, isAuthenticated: true, user }); + // }, + // onError: error => { + // setError(error.message); + // } + // }); + // setTimeout(silentRefresh, 5 * 60 * 1000); + // }, [setUserContext]); + + useEffect(() => { + if (loginUser.isLoading || logoutUser.isLoading) { + setIsLoading(true); + } else { + setIsLoading(false); + } + }, [loginUser.isLoading, logoutUser.isLoading]); + + // useEffect(() => { + // if (token) + // silentRefresh(); + // }, [token, silentRefresh]); + + // Make the provider update only when it should + const memoedValue = useMemo( + () => ({ + user, + token, + isAuthenticated, + isLoading, + error, + login, + logout + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [user, isLoading, error, isAuthenticated, token] + ); + + return {children}; +}; + +const useAuthContext = () => { + const context = useContext(AuthContext); + + if (context === undefined) { + throw new Error('useAuthContext should be used inside AuthProvider'); + } + + return context; +}; + +export { AuthContextProvider, useAuthContext }; diff --git a/client/src/main.jsx b/client/src/main.jsx index b27ce9447..2d48b6df7 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,22 +1,15 @@ import { createRoot } from 'react-dom/client'; -import { RecoilRoot } from 'recoil'; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { ThemeProvider } from './hooks/ThemeContext'; import App from './App'; import './style.css'; import './mobile.css'; +import { ApiErrorBoundaryProvider } from './hooks/ApiErrorBoundaryContext'; const container = document.getElementById('root'); const root = createRoot(container); -const queryClient = new QueryClient(); root.render( - - - - - - - + + + ); diff --git a/client/src/routes/Root.jsx b/client/src/routes/Root.jsx index 2d12d3e24..5ce9b65a0 100644 --- a/client/src/routes/Root.jsx +++ b/client/src/routes/Root.jsx @@ -1,13 +1,49 @@ import React, { useEffect, useState } from 'react'; import { Outlet } from 'react-router-dom'; - import MessageHandler from '../components/MessageHandler'; import Nav from '../components/Nav'; import MobileNav from '../components/Nav/MobileNav'; - +import { useGetSearchEnabledQuery, useGetEndpointsQuery, useGetPresetsQuery } from '~/data-provider'; +import store from '~/store'; +import { useSetRecoilState } from 'recoil'; +import { useAuthContext } from '~/hooks/AuthContext'; export default function Root() { const [navVisible, setNavVisible] = useState(false); + const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled); + const setEndpointsConfig = useSetRecoilState(store.endpointsConfig); + const setPresets = useSetRecoilState(store.presets); + const { user } = useAuthContext(); + + const searchEnabledQuery = useGetSearchEnabledQuery(); + const endpointsQuery = useGetEndpointsQuery(); + const presetsQuery = useGetPresetsQuery({ enabled: !!user }); + + + useEffect(() => { + if (endpointsQuery.data) { + setEndpointsConfig(endpointsQuery.data); + } else if (endpointsQuery.isError) { + console.error('Failed to get endpoints', endpointsQuery.error); + } + }, [endpointsQuery.data, endpointsQuery.isError]); + + useEffect(() => { + if (presetsQuery.data) { + setPresets(presetsQuery.data); + } else if (presetsQuery.isError) { + console.error('Failed to get presets', presetsQuery.error); + } + }, [presetsQuery.data, presetsQuery.isError]); + + useEffect(() => { + if (searchEnabledQuery.data) { + setIsSearchEnabled(searchEnabledQuery.data); + } else if (searchEnabledQuery.isError) { + console.error('Failed to get search enabled', searchEnabledQuery.error); + } + }, [searchEnabledQuery.data, searchEnabledQuery.isError]); + return ( <>
@@ -22,7 +58,6 @@ export default function Root() {
- ); diff --git a/client/src/utils/userAuth.js b/client/src/utils/userAuth.js deleted file mode 100644 index a24dab707..000000000 --- a/client/src/utils/userAuth.js +++ /dev/null @@ -1,23 +0,0 @@ -import axios from 'axios'; - -export default async function fetchData() { - try { - const response = await axios.get('/api/me', { - timeout: 1000, - withCredentials: true - }); - const user = response.data; - if (user) { - // dispatch(setUser(user)); - // callback(user); - return user; - } else { - console.log('Not login!'); - window.location.href = '/auth/login'; - } - } catch (error) { - console.error(error); - console.log('Not login!'); - window.location.href = '/auth/login'; - } -} \ No newline at end of file diff --git a/client/tailwind.config.cjs b/client/tailwind.config.cjs index d4b4d637c..81b27fb00 100644 --- a/client/tailwind.config.cjs +++ b/client/tailwind.config.cjs @@ -39,7 +39,19 @@ module.exports = { '700': '#40414f', // Replacing .dark .dark:bg-gray-700 and .dark .dark:hover:bg-gray-700:hover '800': '#343541', // Replacing .dark .dark:bg-gray-800, .bg-gray-800, and .dark .dark:hover:bg-gray-800\/90 '900': '#202123' // Replacing .dark .dark:bg-gray-900, .bg-gray-900, and .dark .dark:hover:bg-gray-900:hover - } + }, + green: { + 50: "#f1f9f7", + 100: "#def2ed", + 200: "#a6e5d6", + 300: "#6dc8b9", + 400: "#41a79d", + 500: "#10a37f", + 600: "#126e6b", + 700: "#0a4f53", + 800: "#06373e", + 900: "#031f29", + }, } } }, diff --git a/client/vite.config.ts b/client/vite.config.ts index 3e462abcd..5546e30ba 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -17,23 +17,27 @@ export default defineConfig({ '/auth': { target: 'http://localhost:3080', changeOrigin: true + }, + '/oauth': { + target: 'http://localhost:3080', + changeOrigin: true } } }, - plugins: [react(), sourcemapExclude({ excludeNodeModules: true }),], + plugins: [react(), sourcemapExclude({excludeNodeModules: true})], publicDir: './public', build: { sourcemap: true, outDir: './dist', rollupOptions: { output: { - manualChunks: (id) => { - if (id.includes("node_modules")) { - return "vendor"; + manualChunks: id => { + if (id.includes('node_modules')) { + return 'vendor'; } - }, - }, - }, + } + } + } }, resolve: { alias: { diff --git a/client/webpack.config.js b/client/webpack.config.js deleted file mode 100644 index 46b5cb849..000000000 --- a/client/webpack.config.js +++ /dev/null @@ -1,111 +0,0 @@ -const path = require('path'); -// const HtmlWebpackPlugin = require('html-webpack-plugin'); - -/*We are basically telling webpack to take index.js from entry. Then check for all file extensions in resolve. -After that apply all the rules in module.rules and produce the output and place it in main.js in the public folder.*/ - -module.exports = { - /** "mode" - * the environment - development, production, none. tells webpack - * to use its built-in optimizations accordingly. default is production - */ - mode: 'development', - // cache: false, - /** "entry" - * the entry point - */ - entry: './index.js', - output: { - /** "path" - * the folder path of the output file - */ - path: path.resolve(__dirname, 'public'), - /** "filename" - * the name of the output file - */ - filename: 'main.js', - sourceMapFilename: '[name].js.map' - }, - devtool: 'source-map', - /** "target" - * setting "node" as target app (server side), and setting it as "web" is - * for browser (client side). Default is "web" - */ - target: 'web', - devServer: { - /** "port" - * port of dev server - */ - port: '9500', - /** "static" - * This property tells Webpack what static file it should serve - */ - static: ['./public'], - /** "open" - * opens the browser after server is successfully started - */ - open: true, - /** "hot" - * enabling and disabling HMR. takes "true", "false" and "only". - * "only" is used if enable Hot Module Replacement without page - * refresh as a fallback in case of build failures - */ - hot: true, - /** "liveReload" - * disable live reload on the browser. "hot" must be set to false for this to work - */ - liveReload: true - }, - resolve: { - /** "extensions" - * If multiple files share the same name but have different extensions, webpack will - * resolve the one with the extension listed first in the array and skip the rest. - * This is what enables users to leave off the extension when importing - */ - extensions: ['.js', '.jsx', '.json'], - fallback: { - url: require.resolve('url/'), - fs: false, - tls: false, - net: false, - path: false, - zlib: false, - http: false, - https: false, - stream: false, - crypto: false, - 'crypto-browserify': require.resolve('crypto-browserify') //if you want to use this module also don't forget npm i crypto-browserify - } - }, - module: { - /** "rules" - * This says - "Hey webpack compiler, when you come across a path that resolves to a '.js or .jsx' - * file inside of a require()/import statement, use the babel-loader to transform it before you - * add it to the bundle. And in this process, kindly make sure to exclude node_modules folder from - * being searched" - */ - rules: [ - { - test: /\.(js|jsx)$/, //kind of file extension this rule should look for and apply in test - exclude: /node_modules/, //folder to be excluded - use: 'babel-loader' //loader which we are going to use - }, - { - test: /\.css$/i, - include: path.resolve(__dirname, 'src'), - use: ['style-loader', 'css-loader', 'postcss-loader'] - }, - { // source: https://stackoverflow.com/questions/61767538/devtools-failed-to-load-sourcemap-for-webpack-node-modules-js-map-http-e - test: /\.js$/, - enforce: 'pre', - use: ['source-map-loader'], - }, - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/, - }, - ] - } - // plugins: [new HtmlWebpackPlugin()], -};