From 36db5df91f0cb90903a0e2b817ebfc6161513189 Mon Sep 17 00:00:00 2001 From: jasonfoknxu Date: Fri, 6 Jan 2023 04:05:09 +0800 Subject: [PATCH] initial commit --- .env | 14 + .gitignore | 5 + .testing.env | 14 + LICENSE | 21 ++ README.md | 58 +++ package.json | 32 ++ src/app.ts | 40 ++ src/controllers/auth.ts | 160 ++++++++ src/index.ts | 70 ++++ src/middleware/customResponse.ts | 45 +++ src/models/.gitkeep | 0 src/routes/v1/index.ts | 23 ++ src/routes/v1/user.ts | 16 + src/types/authRequest.ts | 7 + src/types/request.ts | 9 + src/types/result.ts | 5 + src/types/token.ts | 4 + src/types/xss-clean.ts | 4 + src/utils/config.ts | 50 +++ src/utils/log.ts | 46 +++ tsconfig.json | 107 ++++++ yarn.lock | 626 +++++++++++++++++++++++++++++++ 22 files changed, 1356 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 .testing.env create mode 100644 LICENSE create mode 100644 README.md create mode 100644 package.json create mode 100644 src/app.ts create mode 100644 src/controllers/auth.ts create mode 100644 src/index.ts create mode 100644 src/middleware/customResponse.ts create mode 100644 src/models/.gitkeep create mode 100644 src/routes/v1/index.ts create mode 100644 src/routes/v1/user.ts create mode 100644 src/types/authRequest.ts create mode 100644 src/types/request.ts create mode 100644 src/types/result.ts create mode 100644 src/types/token.ts create mode 100644 src/types/xss-clean.ts create mode 100644 src/utils/config.ts create mode 100644 src/utils/log.ts create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.env b/.env new file mode 100644 index 0000000..6f73d51 --- /dev/null +++ b/.env @@ -0,0 +1,14 @@ +# Settings +ENABLE_SSL=false + +# Port number +HTTP_PORT=80 +HTTPS_PORT=443 + +# SSL (path of SSL cert) +SSL_CERT=/etc/letsencrypt/live/api.domain.local/fullchain.pem +SSL_KEY=/etc/letsencrypt/live/api.domain.local/privkey.pem + +# Security Information (KEEP IN SECRET) +TOKEN_SECRET=8270E043ADCCB7A1C6A4B0466B88AB190813198A366A4935EB30A270DED4F9CD +TOKEN_TTL=3600 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3236975 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Compiled scripts +/dist + +# Dependencies directory +/node_modules \ No newline at end of file diff --git a/.testing.env b/.testing.env new file mode 100644 index 0000000..4f1b878 --- /dev/null +++ b/.testing.env @@ -0,0 +1,14 @@ +# Settings +ENABLE_SSL=false + +# Port number +HTTP_PORT=8080 +HTTPS_PORT=8443 + +# SSL (path of SSL cert) +SSL_CERT= +SSL_KEY= + +# Security Information (KEEP IN SECRET) +TOKEN_SECRET=AP!_$ECRET +TOKEN_TTL=86400 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..91be417 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 NXU + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a492a7 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# RESTful API Template (TypeScript + Express) + +A very simple RESTful API Template for testing and demonstration. Basic structure only. + +:warning: This template is **NOT** ready for production use! :warning: + +### :notebook: Notices & Limitations +- Database connection is **NOT** implemented in this template +- The auth controller is **NOT** a complete solution :exclamation: +- Routes, controllers and models should be added to the API +- Delete the user route & auth controller if you don't need them + +### :clipboard: Features +- TypeScript, ES2020 version +- Build with Express +- Gzip Compression +- Basic XSS Protection +- CORS-enabled (All Origins) +- Ready to support JSON Web Tokens (JWT) +- Support JSON Request +- Support SSL (HTTPS server) +- Support Config file + +### :memo: How to use? +1. `git clone https://github.com/jasonfoknxu/restful-api-typescript-express-template` to download the source code +2. `yarn` or `npm install` to install all the dependencies +3. Edit the config in the `.env` file (`.testing.env` for testing) +4. Add & modify the controllers, models, routes ... +5. `yarn run build` or `npm run build` to compile the scripts +6. (Optional) `yarn run test` or `npm run test` to run the API server with testing config +7. `yarn start` or `npm start` to start the API server + +### :file_folder: File Structure +``` +. +├── dist # The compiled scripts +├── node_modules # The dependencies and the libraries +├── src # Directory of the source code +│ ├── controllers # Directory for the controllers of the API +│ ├── middleware # Directory for the middleware of the API +│ ├── models # Directory for data models, database structure +│ ├── routes # Directory for the API routes +│ ├── types # The types and interface of TypeScript +│ └── utils # The utilities used in the API +├── .env # The config file of the API +├── .testing.env # The testing config +├── package.json # The Node.js ecosystem file +└── tsconfig.json # Config of TypeScript +``` + +### :bookmark: Credits +- [Express](https://expressjs.com) +- [Dotenv](https://github.com/motdotla/dotenv) +- [Helmet](https://github.com/helmetjs/helmet) +- [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) +- [Node.js compression middleware](https://github.com/expressjs/compression) +- [Node.js CORS middleware](https://github.com/expressjs/cors) +- [XSS-Clean](https://github.com/jsonmaur/xss-clean) \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..4cfe161 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "restful-api-typescript-express-template", + "version": "1.0.0", + "description": "A simple RESTful API template with TypeScript and Express", + "main": "dist/index.js", + "homepage": "https://github.com/jasonfoknxu/restful-api-typescript-express-template", + "author": "jasonfoknxu (https://nxuweb.net)", + "license": "MIT", + "repository": "github:jasonfoknxu/restful-api-typescript-express-template", + "scripts": { + "start": "node dist/index.js", + "build": "tsc -p tsconfig.json", + "test": "yarn start test" + }, + "devDependencies": { + "@types/compression": "^1.7.2", + "@types/cors": "^2.8.13", + "@types/express": "^4.17.15", + "@types/jsonwebtoken": "^9.0.0", + "@types/node": "^18.11.18", + "typescript": "^4.9.4" + }, + "dependencies": { + "compression": "^1.7.4", + "cors": "^2.8.5", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "helmet": "^6.0.1", + "jsonwebtoken": "^9.0.0", + "xss-clean": "^0.1.1" + } +} diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..0737bc2 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,40 @@ +import express from 'express'; +import helmet from 'helmet'; +import xss from 'xss-clean'; +import compression from 'compression'; +import cors from 'cors'; +import routes from './routes/v1'; +import { customResponse } from './middleware/customResponse'; + +const app = express(); + +// Set security HTTP headers +app.use(helmet()); + +// Parse json request body +app.use(express.json()); + +// Parse urlencoded request body +app.use(express.urlencoded({ extended: true })); + +// Sanitize request data +app.use(xss()); + +// Gzip compression +app.use(compression()); + +// Custom response result +app.use(customResponse); + +// Enable cors +app.use(cors()); + +// Default route +app.use('/v1', routes); + +// Response 404 error for any unknown api request +app.use((req, res, next) => { + res.result('API Not found.', 404); +}); + +export default app; diff --git a/src/controllers/auth.ts b/src/controllers/auth.ts new file mode 100644 index 0000000..0582298 --- /dev/null +++ b/src/controllers/auth.ts @@ -0,0 +1,160 @@ +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import config from '../utils/config'; +import { AuthRequest } from '../types/authRequest'; +import { Token } from '../types/token'; + +/** + * Login with username and password (header) + * + * @returns The access token and expire datetime of the token + */ +const login = async (req: Request, res: Response, next: NextFunction) => { + let username: string = req.get('username') ?? ''; + const userPassword: string = req.get('password') ?? ''; + if (!username || !userPassword) { + // No username or password + return res.result('Missing username or password.', 403); + } + username = username.trim(); + let verification = await verifyUser(username, userPassword); + if (!verification) { + return res.result('Invalid username or password.', 401); + } + + let newToken = await generateAccessToken(username); + // If generate token fail + if (!newToken) { + return res.result('Invalid token.', 401); + } + + return res.result({ token: newToken.token, expires: newToken.expiredAt }, 'Login success.'); +}; + +/** + * Verify user account by username and password + * + * @description Please change to your own verification method for the user authentication. ⚠️ This is a insecure demo. NOT for production! + * + * @returns The user verification result + */ +const verifyUser = async (username: string, password: string): Promise => { + return password === Buffer.from(username).toString('base64'); +}; + +/** + * Update the Access Token + * + * @returns The updated access token and the token expire datetime + */ +const reload = async (req: Request, res: Response, next: NextFunction) => { + let newToken: Token | [string, number] = await refreshToken(req, res, next); + if (Array.isArray(newToken)) { + return res.result(newToken[0], newToken[1]); + } + if (!newToken || !newToken.token || !newToken.expiredAt) { + return res.result('Missing or unsupported token.', 406); + } + return res.result({ token: newToken.token, expires: newToken.expiredAt }, 'Token updated.'); +}; + +/** + * Generate Access Token + * + * @returns The access token and expire datetime of the token + */ +const generateAccessToken = async (user: string | undefined): Promise => { + if (!user) { + return null; + } + const token = jwt.sign({ user: user }, config.TOKEN_SECRET, { + expiresIn: `${config.TOKEN_TTL}s`, + }); + return { token: token, expiredAt: getExpiredDate(config.TOKEN_TTL) }; +}; + +/** + * Check Access Token exists and is in supported format + * + * @returns The access token + */ +const checkToken = (req: Request, res: Response, next: NextFunction) => { + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.includes(' ') || authHeader.split(' ')[0] !== 'Bearer') { + return null; + } + const token = authHeader.split(' ')[1]; + if (!token) { + return null; + } + return token; +}; + +/** + * Validate the Access Token + * + * @description ⚠️ This is a incomplete solution, the decoded token should be compared with the database record + */ +const authenticateToken = async (req: AuthRequest, res: Response, next: NextFunction) => { + const token = checkToken(req, res, next); + if (!token) { + return res.result('Missing or unsupported token.', 406); + } + + // Verify with JSON Web Token (JWT) + jwt.verify(token, config.TOKEN_SECRET, async (err, decoded) => { + if (err) { + if (err.name === 'TokenExpiredError') { + // Expired Token + return res.result('Token expired', 403); + } else { + // Token with error + return res.result('Invalid token.', 401); + } + } + if (!decoded) { + return res.result('Token error.', 401); + } + + // Pass the decoded token to the request + req.authorization = decoded; + // Pass the user ID to the request + // req.user = /* User ID */; + next(); + }); +}; + +/** + * Refresh Access Token + * + * @returns The new access token + */ +const refreshToken = async (req: Request, res: Response, next: NextFunction): Promise => { + const token = checkToken(req, res, next); + if (!token) { + return ['Missing or unsupported token.', 406]; + } + const user: string | undefined = req.get('user') ?? undefined; + + // Crate new Access Token + let auth = await generateAccessToken(user); + // If new token generate fail + if (!auth || !auth.token) { + return ['Invalid token.', 401]; + } + + return auth; +}; + +/** + * Calculate the token expire datetime + * + * @returns The expire datetime of the token + */ +const getExpiredDate = (expireTime: number) => { + let now = new Date(); + now.setTime(now.getTime() + expireTime * 1000); + return new Date(now); +}; + +export { login, reload, authenticateToken }; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ee56b53 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,70 @@ +import app from './app'; +import log from './utils/log'; +import config from './utils/config'; +import https from 'https'; +import http from 'http'; +import fs from 'fs'; + +let server: https.Server | http.Server | null = null; + +const createServer = () => { + if (config.ENABLE_SSL && config.SSL_CERT && config.SSL_KEY) { + // Create HTTPS server + try { + server = https + .createServer( + { + key: fs.readFileSync(config.SSL_KEY), + cert: fs.readFileSync(config.SSL_CERT), + }, + app + ) + .listen(config.HTTPS_PORT); + log(`HTTPS Server started. Listening to port ${config.HTTPS_PORT}`, 'i'); + } catch (err) { + log(['HTTPS Server cannot be started.', err], 'e'); + process.exit(1); + } + } else { + // Create HTTP Server + server = app.listen(config.HTTP_PORT, async () => { + log(`HTTP Server started. Listening to port ${config.HTTP_PORT}`, 'i'); + }); + } +}; + +// Close server +const closeServer = () => { + if (server) { + server.close(() => { + log('Server closed. (EXIT)', 'x'); + }); + } +}; + +// Exit process +const exitHandler = () => { + closeServer(); + process.exit(1); +}; + +// Exception handling +const unexpectedErrorHandler = (error: string) => { + log(['Unexpected Error!', error], 'e'); + exitHandler(); +}; + +process.on('uncaughtException', unexpectedErrorHandler); +process.on('unhandledRejection', unexpectedErrorHandler); + +// Kill process +process.on('SIGTERM', () => { + log('Server closed. (SIGTERM received)', 'w'); + closeServer(); +}); + +(() => { + createServer(); +})(); + +export { createServer, closeServer }; diff --git a/src/middleware/customResponse.ts b/src/middleware/customResponse.ts new file mode 100644 index 0000000..e7b6aaf --- /dev/null +++ b/src/middleware/customResponse.ts @@ -0,0 +1,45 @@ +import { Request, Response, NextFunction } from 'express'; +import { Result } from '../types/result'; + +// Add default formatted JSON to Express response +const customResponse = (req: Request, res: Response, next: NextFunction): void => { + function result(message: string, statusCode?: number): void; + function result(resultBody: Result, message?: string, statusCode?: number): void; + + function result(resultBody: Result | string, message?: string | number, statusCode: number = 200): void { + if (statusCode === undefined) { + statusCode = 200; + } + if (typeof resultBody === 'string') { + let result: Result = { + success: 1, + }; + result.message = resultBody; + if (typeof message === 'number') { + statusCode = message; + } + if (statusCode >= 400) { + result.success = 0; + } + resultBody = result; + } else { + let success = 1; + if (typeof message === 'number') { + message = message.toString(); + } + if (message) { + resultBody.message = message; + } + if (statusCode >= 400) { + success = 0; + } + resultBody.success = success; + } + res.status(statusCode).json(resultBody); + } + + res.result = result; + next(); +}; + +export { customResponse }; diff --git a/src/models/.gitkeep b/src/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/v1/index.ts b/src/routes/v1/index.ts new file mode 100644 index 0000000..18e4008 --- /dev/null +++ b/src/routes/v1/index.ts @@ -0,0 +1,23 @@ +import express from 'express'; +import userRoute from './user'; + +const router = express.Router(); + +// Response result of root path +router.all('/', (req, res, next) => { + return res.result('Status: OK'); +}); + +// Add routes to the router +const routes = [ + { + path: '/user', + route: userRoute, + }, +]; + +routes.forEach((route) => { + router.use(route.path, route.route); +}); + +export default router; diff --git a/src/routes/v1/user.ts b/src/routes/v1/user.ts new file mode 100644 index 0000000..dd82db0 --- /dev/null +++ b/src/routes/v1/user.ts @@ -0,0 +1,16 @@ +import express from 'express'; +import * as authController from '../../controllers/auth'; +const router = express.Router(); + +/*** User Authentication / Login ***/ +router.route('/login').post(authController.login); + +/*** Check user login status ***/ +router.route('/check').all(authController.authenticateToken, (req, res, next) => { + return res.result('Logged in.'); +}); + +/*** Manually Refresh Access Token ***/ +router.route('/refresh').post(authController.reload); + +export default router; diff --git a/src/types/authRequest.ts b/src/types/authRequest.ts new file mode 100644 index 0000000..db92f8b --- /dev/null +++ b/src/types/authRequest.ts @@ -0,0 +1,7 @@ +import { Request } from 'express'; +import { JwtPayload } from 'jsonwebtoken'; + +export interface AuthRequest extends Request { + authorization?: JwtPayload | string; + user?: string; +} diff --git a/src/types/request.ts b/src/types/request.ts new file mode 100644 index 0000000..41f468e --- /dev/null +++ b/src/types/request.ts @@ -0,0 +1,9 @@ +import { Response } from 'express'; + +declare global { + namespace Express { + interface Response { + result: any; + } + } +} diff --git a/src/types/result.ts b/src/types/result.ts new file mode 100644 index 0000000..36a0d1e --- /dev/null +++ b/src/types/result.ts @@ -0,0 +1,5 @@ +export interface Result { + message?: string; + success: number; + [key: string]: any; +} diff --git a/src/types/token.ts b/src/types/token.ts new file mode 100644 index 0000000..3bf3415 --- /dev/null +++ b/src/types/token.ts @@ -0,0 +1,4 @@ +export interface Token { + token: string; + expiredAt: Date; +} diff --git a/src/types/xss-clean.ts b/src/types/xss-clean.ts new file mode 100644 index 0000000..cfe0197 --- /dev/null +++ b/src/types/xss-clean.ts @@ -0,0 +1,4 @@ +declare module 'xss-clean' { + const value: Function; + export default value; +} diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 0000000..c950d87 --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,50 @@ +import dotenv from 'dotenv'; +import Path from 'path'; +let envOptions = {}; + +// Load .env config file +if (process.argv) { + const args = process.argv.slice(2); + let envPath = Path.resolve(process.cwd(), '.env'); + switch (args[0]) { + case 'test': + case 'testing': + envPath = Path.resolve(process.cwd(), '.testing.env'); + break; + } + envOptions = { ...envOptions, path: envPath }; +} +const loadConfig = dotenv.config(envOptions); +if (loadConfig.error) { + console.error('[API] Cannot load config'); + throw loadConfig.error; +} + +const loadedConfig = loadConfig.parsed; + +if (!loadedConfig) { + console.error('[API] Cannot load config'); + throw new Error(); +} + +interface Config { + ENABLE_SSL: boolean; + HTTP_PORT: number; + HTTPS_PORT: number; + SSL_CERT: string | null; + SSL_KEY: string | null; + TOKEN_SECRET: string; + TOKEN_TTL: number; +} + +const config: Config = { + ENABLE_SSL: loadedConfig.ENABLE_SSL === 'true', + HTTP_PORT: parseInt(loadedConfig.HTTP_PORT) ?? 80, + HTTPS_PORT: parseInt(loadedConfig.HTTPS_PORT) ?? 443, + SSL_CERT: loadedConfig.SSL_CERT ?? null, + SSL_KEY: loadedConfig.SSL_KEY ?? null, + TOKEN_SECRET: loadedConfig.TOKEN_SECRET ?? '', + TOKEN_TTL: parseInt(loadedConfig.TOKEN_TTL) ?? 3600, +}; + +export default config; diff --git a/src/utils/log.ts b/src/utils/log.ts new file mode 100644 index 0000000..e616e13 --- /dev/null +++ b/src/utils/log.ts @@ -0,0 +1,46 @@ +/** + * Logger to display message in console + */ +const log = (message: string | string[] | object, type: string = '') => { + switch (type.trim()) { + case 'i': + type = 'INFO'; + break; + case 'w': + type = 'WARNING'; + break; + case 'e': + type = 'ERROR'; + break; + case 'x': + type = '!!!'; + break; + case '': + type = 'MESSAGE'; + break; + } + if (Array.isArray(message)) { + console.log('[API] ' + time() + ' | <' + type + '> ' + message[0] + ' | ', message[1]); + } else if (typeof message === 'object' && message !== null) { + console.log('[API] ' + time() + ' | <' + type + '> ', message); + } else { + console.log('[API] ' + time() + ' | <' + type + '> ' + message); + } +}; + +/** + * Get current date time with format YYYY-MM-DD HH:mm:ss + * + * @returns The formatted datetime + */ +const time = (): string => { + const d: Date = new Date(); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart( + 2, + '0' + )} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String( + d.getSeconds() + ).padStart(2, '0')}`; +}; + +export default log; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2e8f3bd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,107 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + "rootDir": "src", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + //"rootDirs": ["src"], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": ["./src/types"], /* Specify multiple folders that act like './node_modules/@types'. */ + "types": ["node"], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": [ + "src/*", + "src/**/*" + ] + } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..fe1cd21 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,626 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/compression@^1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@types/compression/-/compression-1.7.2.tgz#7cc1cdb01b4730eea284615a68fc70a2cdfd5e71" + integrity sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg== + dependencies: + "@types/express" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/cors@^2.8.13": + version "2.8.13" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94" + integrity sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.31": + version "4.17.32" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" + integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.15": + version "4.17.15" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" + integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.31" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/jsonwebtoken@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#4db9bfaf276ef4fdc3608194fab8b8f2fd1c44f9" + integrity sha512-mM4TkDpA9oixqg1Fv2vVpOFyIVLJjm5x4k0V+K/rEsizfjD7Tk7LKk3GTtbB7KCfP0FEHQtsZqFxYA0+sijNVg== + dependencies: + "@types/node" "*" + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/node@*", "@types/node@^18.11.18": + version "18.11.18" + resolved "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz" + integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/serve-static@*": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" + +accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== + 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" + +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + 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" + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +debug@2.6.9: + 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" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +dotenv@^16.0.3: + version "16.0.3" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz" + integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +express@^4.18.2: + version "4.18.2" + resolved "https://registry.npmjs.org/express/-/express-4.18.2.tgz" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + 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" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + 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" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-intrinsic@^1.0.2: + version "1.1.3" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +helmet@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/helmet/-/helmet-6.0.1.tgz" + integrity sha512-8wo+VdQhTMVBMCITYZaGTbE4lvlthelPYSvoyNvk4RECTmrVjMerp9RfUOQXZWLvCcAn1pKj7ZRxK4lI9Alrcw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +jsonwebtoken@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" + integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== + dependencies: + jws "^3.2.2" + lodash "^4.17.21" + ms "^2.1.1" + semver "^7.3.8" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + 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" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +object-assign@^4: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + 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== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + 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" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@^4.9.4: + version "4.9.4" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +xss-clean@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/xss-clean/-/xss-clean-0.1.1.tgz#d3ba684d85ccd52054963d01ad6ab36d662db1a5" + integrity sha512-On99yydxoAEkHpraR7Wjg9cD6UmKfbtH2HXO1it2djid32osHL7Qr8bIu+dGYoOeKzf3ZszLfz1gwR/D7whZ2A== + dependencies: + xss-filters "1.2.6" + +xss-filters@1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/xss-filters/-/xss-filters-1.2.6.tgz#68b39089cb1dff8b9dbc889484839b2f507f5c55" + integrity sha512-uqgwZRpVJCDfHsRX9lDrkPyCitQYzPklmLSbajJncATZKAUd1tF1x9y2VyPNFMv8SsSWed80xorSS5qGpw3WiA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==