diff --git a/api_routes/gpt.js b/api_routes/gpt.js new file mode 100644 index 00000000..c034d009 --- /dev/null +++ b/api_routes/gpt.js @@ -0,0 +1,23 @@ +const router = require('express').Router(); + +router.route('/auth') + .post(async (req, res) => { + const { pw } = req.body; + + if (pw !== process.env.GPT_PASSWORD) { + res.send({ + status: 'error', + message: 'Incorrect password.', + }); + return; + } + + await new Promise((resolve) => setTimeout(resolve, 2000)); + + res.send({ + status: 'success', + token: 'secretToken', + }); + }); + +module.exports = router; diff --git a/api_routes/routes.js b/api_routes/routes.js index 4e8f55c3..2350e338 100644 --- a/api_routes/routes.js +++ b/api_routes/routes.js @@ -8,6 +8,7 @@ const robokache = require('./robokache'); const { handleAxiosError } = require('./utils'); const services = require('./services'); const external_apis = require('./external'); +const gpt_auth = require('./gpt'); const samples = JSON.parse(fs.readFileSync(path.join(__dirname, './sample-query-cache.json'))); @@ -15,6 +16,8 @@ router.use('/', external_apis); router.use('/robokache', robokache.router); +router.use('/gpt', gpt_auth); + router.route('/quick_answer') .post(async (req, res) => { // if this is a sample query, load the response from the cache JSON: diff --git a/package-lock.json b/package-lock.json index afeea6f0..37d23c15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "d3-force": "^2.1.1", "dotenv": "^8.6.0", "express": "^4.17.3", + "express-rate-limit": "^6.8.1", "idb-keyval": "^5.0.6", "js-yaml": "^3.14.0", "lodash": "^4.17.21", @@ -8538,6 +8539,16 @@ "node": ">=8" } }, + "node_modules/boxen/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -12332,6 +12343,15 @@ "node": ">=8" } }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/eslint/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -12735,6 +12755,17 @@ "node": ">= 0.10.0" } }, + "node_modules/express-rate-limit": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.8.1.tgz", + "integrity": "sha512-xJyudsE60CsDShK74Ni1MxsldYaIoivmG3ieK2tAckMsYCBewEuGalss6p/jHmFFnqM9xd5ojE0W2VlanxcOKg==", + "engines": { + "node": ">= 14.0.0" + }, + "peerDependencies": { + "express": "^4 || ^5" + } + }, "node_modules/express/node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -20727,19 +20758,6 @@ "node": ">=8" } }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/meow/node_modules/yargs-parser": { "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", @@ -20999,6 +21017,7 @@ "version": "2.27.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", + "peer": true, "engines": { "node": "*" } @@ -22602,6 +22621,16 @@ "node": ">=8" } }, + "node_modules/np/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/np/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -23479,6 +23508,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ow/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -24753,6 +24792,12 @@ "react": "*" } }, + "node_modules/react-graph-vis/node_modules/uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha512-FULf7fayPdpASncVy4DLh3xydlXEJJpvIELjYjNeQWYUZ9pclcpvCZSr2gkmN2FrrGcI7G/cJsIEwk5/8vfXpg==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details." + }, "node_modules/react-icons": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-3.10.0.tgz", @@ -27341,12 +27386,16 @@ } }, "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "dev": true, + "optional": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/type-is": { @@ -27374,6 +27423,20 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/ua-parser-js": { "version": "0.7.28", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", @@ -27843,10 +27906,13 @@ } }, "node_modules/uuid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details." + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } }, "node_modules/v8-compile-cache": { "version": "2.1.1", @@ -27940,7 +28006,15 @@ "vis-util": "^1.1.6" } }, - "node_modules/vis-util": { + "node_modules/vis-network/node_modules/moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", + "engines": { + "node": "*" + } + }, + "node_modules/vis-network/node_modules/vis-util": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/vis-util/-/vis-util-1.1.10.tgz", "integrity": "sha512-8hGSxsFi2ogYYweClQyITzWnirWgQ8p0i9M4d3OXMuUO8vjXrf+2zHOYI9OZbtUduxAWuMEePnS9BXDtPJmJ7Q==", @@ -27956,12 +28030,17 @@ "url": "https://opencollective.com/visjs" } }, - "node_modules/vis-util/node_modules/moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", + "node_modules/vis-util": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/vis-util/-/vis-util-4.3.4.tgz", + "integrity": "sha512-hJIZNrwf4ML7FYjs+m+zjJfaNvhjk3/1hbMdQZVnwwpOFJS/8dMG8rdbOHXcKoIEM6U5VOh3HNpaDXxGkOZGpw==", + "peer": true, "engines": { - "node": "*" + "node": ">=8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" } }, "node_modules/vis-uuid": { diff --git a/package.json b/package.json index b09a292b..d39759b4 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "d3-force": "^2.1.1", "dotenv": "^8.6.0", "express": "^4.17.3", + "express-rate-limit": "^6.8.1", "idb-keyval": "^5.0.6", "js-yaml": "^3.14.0", "lodash": "^4.17.21", diff --git a/server.js b/server.js index 48894eaf..a4589ce7 100644 --- a/server.js +++ b/server.js @@ -5,6 +5,7 @@ const bodyParser = require('body-parser'); // standard express logger const morgan = require('morgan'); const path = require('path'); +const rateLimit = require('express-rate-limit'); // load env variables dotenv.config(); @@ -19,8 +20,25 @@ const PORT = process.env.PORT || 7080; axios.defaults.maxContentLength = Infinity; axios.defaults.maxBodyLength = Infinity; +// GPT auth: 300 reqs/hr +const gptAuthLimiter = rateLimit({ + windowMs: 1 * 60 * 60 * 1000, // 1 hour + max: 300, // 300 request per window + standardHeaders: true, + legacyHeaders: false, +}); +// GPT: 60 req/min +const gptLimiter = rateLimit({ + windowMs: 1 * 60 * 1000, // 1 minute + max: 60, // 60 request per window + standardHeaders: true, + legacyHeaders: false, +}); + app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json({ limit: '4000mb' })); +app.use('/api/gpt', gptLimiter); +app.use('/api/gpt/auth', gptAuthLimiter); app.use(morgan('dev')); // Add routes @@ -36,5 +54,6 @@ app.get('*', (req, res) => { }); app.listen(PORT, () => { + // eslint-disable-next-line no-console console.log(`🌎 ==> qgraph running on port ${PORT}!`); }); diff --git a/src/App.jsx b/src/App.jsx index 72f2bda2..e8b98567 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -24,12 +24,15 @@ import API from '~/API'; import AlertContext from '~/context/alert'; import BiolinkContext from '~/context/biolink'; +import GPTContext from '~/context/gpt'; import useBiolinkModel from '~/stores/useBiolinkModel'; +import useGPT from '~/stores/useGPT'; export default function App() { const [alert, setAlert] = useState({}); const biolink = useBiolinkModel(); + const gpt = useGPT(); function simpleSetAlert(severity, msg) { setAlert({ severity, msg }); @@ -60,44 +63,46 @@ export default function App() { > - - - simpleSetAlert(alert.severity, '')} - /> -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
-