├── .dockerignore ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .nvmrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── api └── prefect-auth-proxy.postman_collection.json ├── config ├── permitted-routes.json └── permitted-routes.schema.json ├── data ├── README.md ├── mysql │ └── db_init.sql └── postgres │ └── db_init.sql ├── index.js ├── package-lock.json └── package.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .git 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Local Development 107 | localDevelopment 108 | docker-compose.yaml 109 | .DS_Store 110 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/Iron 2 | # specify that we want the LTS version of node. -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue with the owners of this repository before making a change. 4 | 5 | ## Contributing to Development 6 | 7 | Issues will be labelled with `help wanted` or `good first issue` 8 | 9 | - `Help wanted` label indicates tasks where the project team would appreciate community help 10 | - `Good first issue` label indicates a task that introduce developers to the project, have low complexity, and are isolated 11 | 12 | ## Version Control 13 | 14 | The project uses git as its version control system and GitHub as the central server and collaboration platform. 15 | 16 | ### Branching model 17 | 18 | This repository is maintained to enable trunk based development with just one branch, `main` kept active. For all active development, 19 | create a branch off of main and push a PR to merge to main. 20 | 21 | ### Versioning 22 | 23 | Any release from main will have a unique version 24 | 25 | `MAJOR.MINOR.PATCH` will be incremented by: 26 | 27 | 1. `MAJOR` version when breaking changes occur 28 | 2. `MINOR` version with new functionality that is backwards-compatible 29 | 3. `PATCH` version with backwards-compatible bug fixes 30 | 31 | ## Pull Request Process 32 | 33 | 1. All work must be done in a fork off the main branch 34 | 2. Ensure any install or build dependencies are removed 35 | 3. Increase version numbers [accordingly](#versioning) 36 | 4. Docker container should build without error 37 | 5. No dependencies outside package.json 38 | 6. Build and test (currently do not have automated tests, so please double check not to break any current functionality) 39 | 7. Open pull request to merge to main branch 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:current-alpine 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | #COPY package*.json ./ 8 | COPY . ./ 9 | RUN npm ci --omit=dev 10 | RUN npm i -g pm2 11 | 12 | # Bundle app source 13 | #OPY . . 14 | 15 | EXPOSE 3000 16 | CMD [ "pm2-runtime", "start", "index.js" ] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Softrams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proxy Authorization Service for Prefect UI and Prefect CLI 2 | 3 | ![Docker Cloud Automated build](https://img.shields.io/docker/cloud/automated/softrams/prefect-auth-proxy) 4 | 5 | [Prefect](https://prefect.io) is a great platform for building data flows/pipelines. It supports hybrid execution with execution engines running on-premises with 6 | all command control and monitoring capabilities via the Prefect UI hosted on the cloud. This means metadata about flows, logs and metrics 7 | will be stored in the cloud and can be accessed via the UI. When possible, we do recommend using the Prefect Cloud to manage your data flows. 8 | 9 | However some organizations may prefer or required to run their flows on-premises and have the UI run on-premises as well. This is where the 10 | Proxy Authorization Service comes in. Prefect currently do not bundle Authentication/Authorization options for API in the open source eco-system. 11 | This functionality is only available with Prefect Cloud, along with many other features. 12 | 13 | If your deployments have any constraints or security regulations to use Prefect Cloud, this Proxy service could be used as a workaround for 14 | Prefect UI and CLI to enable authentication/authorization for API. All the requests to Prefect API will be proxied through this service. 15 | 16 | ## Setup 17 | 18 | This initial implementation uses API Key and MySQL/Postgres database backend to authenticate and authorize requests. 19 | 20 | ### Setup Database 21 | 22 | This service looks for prefect_api_keys table in MYSQL/Postgres Database. Please refer to data/``/db_init.sql for the initial schema and sample data. 23 | 24 | ```sql 25 | -- create table prefect_api_keys 26 | CREATE TABLE `prefect_api_keys` ( 27 | `user_id` varchar(128) NOT NULL, 28 | `scopes` varchar(2000) NOT NULL, 29 | `api_key` varchar(128) NOT NULL, 30 | `key_issu_dt` datetime NOT NULL, 31 | `key_expr_dt` datetime NOT NULL, 32 | `creatd_dt` datetime NOT NULL, 33 | `last_updatd_dt` datetime NOT NULL, 34 | `creatd_by` varchar(11) NOT NULL, 35 | `last_updatd_by` varchar(11) NOT NULL, 36 | PRIMARY KEY (`user_id`) 37 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 38 | 39 | -- Sample Test Data 40 | insert into prefect_api_keys (user_id, scopes, api_key, key_issu_dt, key_expr_dt, creatd_dt, last_updatd_dt, creatd_by, last_updatd_by) 41 | values('USERID', 'mutation/*, query/*', 'TestKey001', '2021-12-27 00:00:00', '2022-12-31 00:00:00', '2021-12-30 00:00:00', '2021-12-30 00:00:00', 'ADMINUSERID', 'ADMINUSERID') 42 | ``` 43 | 44 | Pass database credentials as environment variables to the service. Please see section below for specific environment variables. 45 | 46 | ### Environment Variables 47 | 48 | The following Environment Variables are available to configure the Proxy service: 49 | 50 | | Option | Default | Description | 51 | | ----------------------------- | ------------------- | ----------------------------------------- | 52 | | `ALLOW_PUBLIC_ACCESS` | `false` | Allow public access to the service | 53 | | `TENANT_ID` | `Default` | Tenant ID | 54 | | `TENANT_NAME` | `Default` | Tenant name | 55 | | `TENANT_SLUG` | `default` | Tenant slug | 56 | | `LOG_LEVEL` | `warn` | Default Log level (`error`,`warn`,`info`) | 57 | | `API_SERVICE_URL` | `` | Prefect API Service URL | 58 | | `HOST` | `0.0.0.0` | Host | 59 | | `PORT` | `3000` | Port | 60 | | `LOG_LEVEL_OVERRIDE_DURATION` | `300` | Log level override duration (seconds) | 61 | | `ENV` | `NA` | Environment Name | 62 | | `DB_TYPE` | `postgres` | Database Type [postgres/mysql] | 63 | | `DB_HOST` | `NULL` | Database Server Host | 64 | | `DB_USER` | `NULL` | Database Server User | 65 | | `DB_PASSWORD` | `NULL` | Database Server Password | 66 | | `DB_DATABASE` | `NULL` | Database Name | 67 | 68 | ### Docker Image 69 | 70 | #### Use pre-built docker image 71 | 72 | Use docker image from Docker hub 73 | 74 | ```bash 75 | # Run using pre-built docker image from docker hub 76 | # Prepare environment variables in .env file 77 | docker run -d -p 3000:3000 --env-file ./.env --name auth-proxy softrams/prefect-auth-proxy:latest 78 | ``` 79 | 80 | #### Build a local docker image 81 | 82 | Build a local docker image (with any changes as needed) and publish to your own repository. 83 | 84 | ```bash 85 | # Build local docker image 86 | docker build -t prefect-auth-proxy . 87 | # Run local docker image 88 | # Prepare environment variables in .env file 89 | docker run -d -p 3000:3000 --env-file ./.env --name auth-proxy prefect-auth-proxy 90 | ``` 91 | 92 | ### Running the service 93 | 94 | This is a typical node.js application. So you can simply clone this repo and run. 95 | 96 | ```bash 97 | # Clone the repo 98 | git clone https://github.com/softrams/prefect-auth-proxy.git prefect-auth-proxy 99 | 100 | # install dependencies 101 | cd prefect-auth-proxy 102 | npm install 103 | 104 | # Run the service 105 | node index.js 106 | ``` 107 | 108 | #### Production Environments 109 | 110 | Depending on the environment and how you are deploying Prefect, there are multiple ways to run the service. 111 | 112 | - This is simply a Nodejs application. So you can also run it as a service using `pm2` or `nodemon` for production deployments. 113 | - If you are running the service on a Kubernetes cluster, use the docker container and deploy as the ingress proxy service. 114 | 115 | ## Sample Initial Access Control Policy 116 | 117 | Here is a sample initial access control policy for the Proxy service. You can use this policy to configure the access control policy for your Prefect UI and CLI. 118 | 119 | ### All Team Members 120 | 121 | All team members will receive Read Only access to all aspects 122 | 123 | ```bash 124 | query/* 125 | ``` 126 | 127 | ### Model Operations, Development and QA Teams 128 | 129 | Only select mutations are allowed for Model Operations Team and Development Team Members 130 | 131 | ```bash 132 | # Run a workflow 133 | mutation/create_flow_run 134 | 135 | # Cancel a flow run 136 | mutation/cancel_flow_run 137 | 138 | # Restart a run 139 | mutation/set_flow_run_states 140 | 141 | ``` 142 | 143 | ### DevOps Team 144 | 145 | For DevOps Team, all mutations are allowed. 146 | 147 | ```bash 148 | mutation/* 149 | ``` 150 | 151 | ## How to use 152 | 153 | ### Create API Keys and distribute to users/services 154 | 155 | API key will be used to authenticate and authorize the requests. Create an API key by inserting an entry to the prefect_api_keys table. Please refer to data/db_init.sql for the initial schema and sample data. 156 | 157 | ### Using API Key via Browser 158 | 159 | Run the following command from Browser Developer Tools console: 160 | 161 | ```bash 162 | let auth = (document.getElementsByTagName('a')[0].__vue__.$store.state).user 163 | auth.user.id='' 164 | ``` 165 | 166 | Once this is done, you can access the Prefect UI just like you would access the Prefect UI regularly. 167 | 168 | > **Note:** Please note that, if you are NOT authorized to do certain things from UI, you will NOT see any error messages, but the request will silently fail. 169 | 170 | ### Using API Key with CLI 171 | 172 | > **Note:** Using API Key with CLI is restricted to DevOps team and automated CI/CD pipelines currently, as it requires wildcard permissions to support all operations. All other teams can still use CLI to query the data, but will not be permitted to create/run workflows or administer Prefect environments. Use Prefect UI to review logs and restart workflows. 173 | 174 | ```bash 175 | # Point to appropriate Prefect cloud server for the target environment 176 | # These are the default values for Prefect in DEV environment 177 | export PREFECT__CLOUD__ENDPOINT="" 178 | export PREFECT__CLOUD__GRAPHQL="/graphql" 179 | 180 | # Set server to cloud 181 | prefect backend cloud 182 | 183 | # Authenticate with API Key 184 | prefect auth login -k 185 | 186 | # Alternately you can also set API Key to use by 187 | # configuring this info in your .prefect.yml as follows 188 | [API_HOST] 189 | api_key = "" 190 | tenant_id = "" 191 | 192 | # Now you are all set. Run any Prefect command from CLI 193 | 194 | # List projects on the cloud server 195 | prefect get projects 196 | 197 | ``` 198 | 199 | ## Enable Verbose Logging 200 | 201 | ### Set Log Level permanently 202 | 203 | To enable verbose logging, set the `LOG_LEVEL` to `info`. 204 | This will log all requests and responses along with headers and other information. 205 | 206 | ### Set Log Level temporarily 207 | 208 | It is possible to dynamically set log level to `info` for 5 minutes by invoking the `/logs` endpoint. This 5 minute duration can be overridden by setting the `LOG_LEVEL_OVERRIDE_DURATION` to a different value. 209 | 210 | ```bash 211 | # sets log level to info for 5 minutes 212 | curl --location --request POST 'http://localhost:3000/logs/info' 213 | ``` 214 | 215 | ## Cache Management 216 | 217 | For better performance, API Key lookups are cached for 30 minutes. Cache expiration is checked every 5 minutes 218 | and any expired keys are removed from the cache. 219 | 220 | For testing purposes, you may clear the 221 | entire cache or delete a specific api key from cache. 222 | 223 | > **Note:** When multiple instances of this proxy are run (behing a load balancer), the cache is only cleared 224 | > from the instance that process the request. There is no guarantee that the cache will be cleared from all instances. 225 | > You may try multiple requests to make sure the cache is cleared in all instances. 226 | 227 | ### Clear Cache 228 | 229 | Clear cache removes all cached API Key lookups. 230 | 231 | ```bash 232 | curl --location --request POST 'http://localhost:3000/cache/reset' 233 | ``` 234 | 235 | ### Delete Specific API Key from Cache 236 | 237 | Use this operation for testing any changes made to API Key by deleting 238 | just a specific key from the cache. 239 | 240 | ```bash 241 | curl --location --request DELETE 'http://localhost:3000/cache/' 242 | ``` 243 | 244 | ### Audit Trail 245 | 246 | Audit trail logs are enabled specifically for all allowed and blocked mutations. Search for `PREFECT_AUDIT_TRAIL` in the logs to see the audit trail logs. 247 | Here is an example of the audit trail log: 248 | 249 | ``` 250 | PREFECT_AUDIT_TRAIL: BLOCKED Mutation create_flow_run for { 251 | operationName: 'CreateFlowRun', 252 | variables: { id: '3e216621-8e22-4f7f-af69-284c644cba05' }, 253 | query: 'mutation CreateFlowRun($context: JSON, $id: UUID!, $flowRunName: String, $parameters: JSON, $scheduledStartTime: DateTime, $runConfig: JSON, $labels: [String!]) {\n' + 254 | ' create_flow_run(\n' + 255 | ' input: {context: $context, flow_id: $id, flow_run_name: $flowRunName, parameters: $parameters, scheduled_start_time: $scheduledStartTime, run_config: $runConfig, labels: $labels}\n' + 256 | ' ) {\n' + 257 | ' id\n' + 258 | ' __typename\n' + 259 | ' }\n' + 260 | '}\n' 261 | } 262 | ``` 263 | 264 | ## Roadmap 265 | 266 | This is the first version of the Prefect API Proxy. This version is ready for use with the required setup as explained above. 267 | You may fork this repository and make your own changes to the code to expand to fit your usecases. 268 | If its generic enough, please consider contributing to the project. 269 | 270 | Here is a summary of the planned features: 271 | 272 | | Feature | Description | 273 | | --------------------------------------- | --------------------------------------------------------------- | 274 | | Options for other Databases | Add additional options for Databases and make this configurable | 275 | | Automated Tests | Add automated tests for the Proxy Service | 276 | | Automated CI/CD pipelines | Add automated CI/CD pipelines and publish Docker Image | 277 | | Documentation for different deployments | Add documentation for different deployments | 278 | 279 | ## Credits 280 | 281 | Our teams found Prefect to be an amazing framework, developed by [Prefect Technologies Inc.](https://prefect.io) and is licensed under the Apache 2.0 License. 282 | Thanks a lot for the great work! 283 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The following versions have been thoroughly scanned and tested. 6 | Please make sure to continuously scan and test for new vulnerability at each build. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 0.0.2 | :white_check_mark: | 11 | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | Please report any vulnerabilities to opensource@softrams.com. One of the key contributors 16 | will respond within 24 hours with an update. 17 | 18 | If there is a vulnerability detected in one of the dependencies, 19 | it will be patched, once a patch is available. 20 | -------------------------------------------------------------------------------- /api/prefect-auth-proxy.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "16b4b633-a4b3-4236-8688-8dbad185aa5c", 4 | "name": "Prefect UI Proxy", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Query : Multiple Query Operations", 10 | "request": { 11 | "method": "POST", 12 | "header": [ 13 | { 14 | "key": "x-prefect-user-id", 15 | "value": "TestKey", 16 | "type": "text" 17 | } 18 | ], 19 | "body": { 20 | "mode": "raw", 21 | "raw": "[\n {\n \"operationName\": \"FlowRuns\",\n \"variables\": {\n \"projectId\": null,\n \"heartbeat\": \"2021-12-29T14:00:00Z\"\n },\n \"query\": \"query FlowRuns($projectId: uuid, $heartbeat: timestamptz) {\\n Success: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Success\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Running: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Running\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Pending: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Pending\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Failed: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Failed\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n ClientFailed: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"ClientFailed\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Submitted: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Submitted\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Queued: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Queued\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Resume: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Resume\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Retrying: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Retrying\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Looped: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Looped\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Cached: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Cached\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Mapped: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Mapped\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n TimedOut: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"TimedOut\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n TriggerFailed: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"TriggerFailed\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Skipped: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Skipped\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Finished: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Finished\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Cancelled: flow_run_aggregate(\\n where: {flow: {project_id: {_eq: $projectId}}, scheduled_start_time: {_gte: $heartbeat}, state: {_eq: \\\"Cancelled\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"InProgressFlowRuns\",\n \"variables\": {\n \"projectId\": null\n },\n \"query\": \"query InProgressFlowRuns($projectId: uuid) {\\n flow_run(\\n where: {flow: {project_id: {_eq: $projectId}}, state: {_in: [\\\"Running\\\", \\\"Submitted\\\", \\\"Cancelling\\\"]}}\\n order_by: {start_time: desc}\\n ) {\\n id\\n name\\n start_time\\n state\\n state_timestamp\\n flow {\\n id\\n name\\n flow_group_id\\n __typename\\n }\\n updated\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"Agents\",\n \"variables\": {},\n \"query\": \"query Agents {\\n agent {\\n id\\n agent_config_id\\n core_version\\n created\\n name\\n labels\\n last_queried\\n type\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"Projects\",\n \"variables\": {},\n \"query\": \"query Projects {\\n project {\\n id\\n name\\n tenant_id\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"Flows\",\n \"variables\": {},\n \"query\": \"query Flows {\\n flow(where: {archived: {_eq: false}}) {\\n id\\n flow_group_id\\n name\\n project_id\\n is_schedule_active\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"UpcomingFlowRuns\",\n \"variables\": {\n \"projectId\": null\n },\n \"query\": \"query UpcomingFlowRuns($projectId: uuid) {\\n flow_run(\\n where: {flow: {project_id: {_eq: $projectId}}, state: {_eq: \\\"Scheduled\\\"}}\\n order_by: [{scheduled_start_time: asc}, {flow: {name: asc}}]\\n ) {\\n id\\n name\\n state\\n labels\\n scheduled_start_time\\n version\\n flow {\\n id\\n name\\n schedule\\n environment\\n is_schedule_active\\n __typename\\n }\\n __typename\\n }\\n}\\n\"\n }\n]", 22 | "options": { 23 | "raw": { 24 | "language": "json" 25 | } 26 | } 27 | }, 28 | "url": { 29 | "raw": "localhost:3000/graphql", 30 | "host": [ 31 | "localhost" 32 | ], 33 | "port": "3000", 34 | "path": [ 35 | "graphql" 36 | ] 37 | } 38 | }, 39 | "response": [] 40 | }, 41 | { 42 | "name": "Create Flow Run", 43 | "request": { 44 | "method": "POST", 45 | "header": [ 46 | { 47 | "key": "x-prefect-user-id", 48 | "value": "TestKey001", 49 | "type": "text" 50 | } 51 | ], 52 | "body": { 53 | "mode": "raw", 54 | "raw": "[\n {\n \"operationName\": \"CreateFlowRun\",\n \"variables\": {\n \"id\": \"3e216621-8e22-4f7f-af69-284c644cba05\"\n },\n \"query\": \"mutation CreateFlowRun($context: JSON, $id: UUID!, $flowRunName: String, $parameters: JSON, $scheduledStartTime: DateTime, $runConfig: JSON, $labels: [String!]) {\\n create_flow_run(\\n input: {context: $context, flow_id: $id, flow_run_name: $flowRunName, parameters: $parameters, scheduled_start_time: $scheduledStartTime, run_config: $runConfig, labels: $labels}\\n ) {\\n id\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"Flow\",\n \"variables\": {\n \"id\": \"756e7c2a-12f6-4d91-8cdb-7543810a6d9b\"\n },\n \"query\": \"query Flow($id: uuid!) {\\n flow_group_by_pk(id: $id) {\\n id\\n labels\\n created\\n description\\n name\\n default_parameters\\n schedule\\n flows {\\n id\\n archived\\n core_version\\n created\\n description\\n run_config\\n environment\\n flow_group_id\\n is_schedule_active\\n name\\n parameters\\n project {\\n id\\n name\\n __typename\\n }\\n schedule\\n storage\\n updated\\n version\\n version_group_id\\n __typename\\n }\\n settings\\n updated\\n __typename\\n }\\n}\\n\"\n }\n]", 55 | "options": { 56 | "raw": { 57 | "language": "json" 58 | } 59 | } 60 | }, 61 | "url": { 62 | "raw": "localhost:3000/graphql", 63 | "host": [ 64 | "localhost" 65 | ], 66 | "port": "3000", 67 | "path": [ 68 | "graphql" 69 | ] 70 | } 71 | }, 72 | "response": [] 73 | }, 74 | { 75 | "name": "Create Flow Run (With Multiple Queries)", 76 | "request": { 77 | "method": "POST", 78 | "header": [ 79 | { 80 | "key": "x-prefect-user-id", 81 | "value": "TestKey", 82 | "type": "text" 83 | } 84 | ], 85 | "body": { 86 | "mode": "raw", 87 | "raw": "[\n {\n \"operationName\": \"TableFlowRuns\",\n \"variables\": {\n \"limit\": 15,\n \"name\": null,\n \"offset\": 0,\n \"state\": null,\n \"orderBy\": {\n \"scheduled_start_time\": \"desc\"\n },\n \"flow_group_id\": \"756e7c2a-12f6-4d91-8cdb-7543810a6d9b\"\n },\n \"query\": \"query TableFlowRuns($name: String, $limit: Int, $offset: Int, $orderBy: [flow_run_order_by!], $flow_group_id: uuid, $flow_id: uuid, $state: [String!]) {\\n flow_run(\\n where: {flow: {flow_group_id: {_eq: $flow_group_id}, id: {_eq: $flow_id}}, name: {_ilike: $name}, state: {_in: $state, _neq: \\\"Scheduled\\\"}}\\n order_by: $orderBy\\n limit: $limit\\n offset: $offset\\n ) {\\n id\\n name\\n end_time\\n scheduled_start_time\\n start_time\\n state\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"CreateFlowRun\",\n \"variables\": {\n \"context\": {},\n \"id\": \"3e216621-8e22-4f7f-af69-284c644cba05\",\n \"flowRunName\": \"curly-dragonfly\",\n \"parameters\": {\n \"P_TEMP_TABLE_1\": \"tmp_1800_inbound_rawdata\",\n \"P_TEMP_TABLE_2\": \"MDE_BENE_INTRFC_tmp_1800\",\n \"P_inb_filename\": \"DPRF.NGD1800.D211214.T2232008\"\n },\n \"scheduledStartTime\": null,\n \"runConfig\": {\n \"env\": null,\n \"type\": \"KubernetesRun\",\n \"image\": \"molsonlabs/prefect-snowflake:0.0.1\",\n \"labels\": [],\n \"cpu_limit\": null,\n \"__version__\": \"0.15.6\",\n \"cpu_request\": null,\n \"job_template\": null,\n \"memory_limit\": null,\n \"memory_request\": null,\n \"image_pull_policy\": null,\n \"job_template_path\": null,\n \"image_pull_secrets\": null,\n \"service_account_name\": null\n },\n \"labels\": null\n },\n \"query\": \"mutation CreateFlowRun($context: JSON, $id: UUID!, $flowRunName: String, $parameters: JSON, $scheduledStartTime: DateTime, $runConfig: JSON, $labels: [String!]) {\\n create_flow_run(\\n input: {context: $context, flow_id: $id, flow_run_name: $flowRunName, parameters: $parameters, scheduled_start_time: $scheduledStartTime, run_config: $runConfig, labels: $labels}\\n ) {\\n id\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"TableFlowRunsCount\",\n \"variables\": {\n \"name\": null,\n \"state\": null,\n \"flow_group_id\": \"756e7c2a-12f6-4d91-8cdb-7543810a6d9b\"\n },\n \"query\": \"query TableFlowRunsCount($flow_group_id: uuid, $flow_id: uuid, $name: String, $state: [String!]) {\\n flow_run_aggregate(\\n where: {flow: {flow_group_id: {_eq: $flow_group_id}, id: {_eq: $flow_id}}, name: {_ilike: $name}, state: {_in: $state, _neq: \\\"Scheduled\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"FlowRuns\",\n \"variables\": {\n \"heartbeat\": \"2021-12-29T15:00:00Z\",\n \"flow_group_id\": \"756e7c2a-12f6-4d91-8cdb-7543810a6d9b\"\n },\n \"query\": \"query FlowRuns($flow_group_id: uuid, $flow_id: uuid, $heartbeat: timestamptz) {\\n Success: flow_run_aggregate(\\n where: {flow: {flow_group_id: {_eq: $flow_group_id}, id: {_eq: $flow_id}}, updated: {_gte: $heartbeat}, state: {_eq: \\\"Success\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Failed: flow_run_aggregate(\\n where: {flow: {flow_group_id: {_eq: $flow_group_id}, id: {_eq: $flow_id}}, updated: {_gte: $heartbeat}, state: {_eq: \\\"Failed\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Submitted: flow_run_aggregate(\\n where: {flow: {flow_group_id: {_eq: $flow_group_id}, id: {_eq: $flow_id}}, updated: {_gte: $heartbeat}, state: {_eq: \\\"Submitted\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Paused: flow_run_aggregate(\\n where: {flow: {flow_group_id: {_eq: $flow_group_id}, id: {_eq: $flow_id}}, updated: {_gte: $heartbeat}, state: {_eq: \\\"Paused\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Resume: flow_run_aggregate(\\n where: {flow: {flow_group_id: {_eq: $flow_group_id}, id: {_eq: $flow_id}}, updated: {_gte: $heartbeat}, state: {_eq: \\\"Resume\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Running: flow_run_aggregate(\\n where: {flow: {flow_group_id: {_eq: $flow_group_id}, id: {_eq: $flow_id}}, updated: {_gte: $heartbeat}, state: {_eq: \\\"Running\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n Cancelled: flow_run_aggregate(\\n where: {flow: {flow_group_id: {_eq: $flow_group_id}, id: {_eq: $flow_id}}, updated: {_gte: $heartbeat}, state: {_eq: \\\"Cancelled\\\"}}\\n ) {\\n aggregate {\\n count\\n __typename\\n }\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"Agents\",\n \"variables\": {},\n \"query\": \"query Agents {\\n agent {\\n id\\n agent_config_id\\n core_version\\n created\\n name\\n labels\\n last_queried\\n type\\n __typename\\n }\\n}\\n\"\n }\n]", 88 | "options": { 89 | "raw": { 90 | "language": "json" 91 | } 92 | } 93 | }, 94 | "url": { 95 | "raw": "localhost:3000/graphql", 96 | "host": [ 97 | "localhost" 98 | ], 99 | "port": "3000", 100 | "path": [ 101 | "graphql" 102 | ] 103 | } 104 | }, 105 | "response": [] 106 | }, 107 | { 108 | "name": "Cancel Flow Run", 109 | "request": { 110 | "method": "POST", 111 | "header": [ 112 | { 113 | "key": "x-prefect-user-id", 114 | "value": "TestKey", 115 | "type": "text" 116 | } 117 | ], 118 | "body": { 119 | "mode": "raw", 120 | "raw": "[\n {\n \"operationName\": \"Projects\",\n \"variables\": {},\n \"query\": \"query Projects {\\n project {\\n id\\n name\\n tenant_id\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"Flows\",\n \"variables\": {},\n \"query\": \"query Flows {\\n flow(where: {archived: {_eq: false}}) {\\n id\\n flow_group_id\\n name\\n project_id\\n is_schedule_active\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"Agents\",\n \"variables\": {},\n \"query\": \"query Agents {\\n agent {\\n id\\n agent_config_id\\n core_version\\n created\\n name\\n labels\\n last_queried\\n type\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"FlowRun\",\n \"variables\": {\n \"id\": \"2b176b8f-0d89-4780-98e5-5123598cf7f9\"\n },\n \"query\": \"query FlowRun($id: uuid!) {\\n flow_run_by_pk(id: $id) {\\n id\\n name\\n version\\n labels\\n auto_scheduled\\n auto_scheduled\\n context\\n end_time\\n flow_id\\n name\\n parameters\\n scheduled_start_time\\n start_time\\n state\\n state_message\\n state_timestamp\\n parameters\\n context\\n agent_id\\n states {\\n id\\n state\\n timestamp\\n start_time\\n __typename\\n }\\n flow {\\n id\\n name\\n version\\n version_group_id\\n core_version\\n parameters\\n archived\\n flow_group_id\\n project {\\n id\\n name\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n}\\n\"\n },\n {\n \"operationName\": \"CancelFlowRun\",\n \"variables\": {\n \"flowRunId\": \"2b176b8f-0d89-4780-98e5-5123598cf7f9\"\n },\n \"query\": \"mutation CancelFlowRun($flowRunId: UUID!) {\\n cancel_flow_run(input: {flow_run_id: $flowRunId}) {\\n state\\n __typename\\n }\\n}\\n\"\n }\n]", 121 | "options": { 122 | "raw": { 123 | "language": "json" 124 | } 125 | } 126 | }, 127 | "url": { 128 | "raw": "localhost:3000/graphql", 129 | "host": [ 130 | "localhost" 131 | ], 132 | "port": "3000", 133 | "path": [ 134 | "graphql" 135 | ] 136 | } 137 | }, 138 | "response": [] 139 | }, 140 | { 141 | "name": "Healthcheck", 142 | "request": { 143 | "method": "GET", 144 | "header": [], 145 | "url": { 146 | "raw": "http://localhost:3000/healthcheck", 147 | "protocol": "http", 148 | "host": [ 149 | "localhost" 150 | ], 151 | "port": "3000", 152 | "path": [ 153 | "healthcheck" 154 | ] 155 | } 156 | }, 157 | "response": [] 158 | }, 159 | { 160 | "name": "Reset Cache", 161 | "request": { 162 | "method": "POST", 163 | "header": [], 164 | "url": { 165 | "raw": "http://localhost:3000/cache/reset", 166 | "protocol": "http", 167 | "host": [ 168 | "localhost" 169 | ], 170 | "port": "3000", 171 | "path": [ 172 | "cache", 173 | "reset" 174 | ] 175 | } 176 | }, 177 | "response": [] 178 | }, 179 | { 180 | "name": "Delete Key from Cache", 181 | "request": { 182 | "method": "DELETE", 183 | "header": [], 184 | "url": { 185 | "raw": "http://localhost:3000/cache/:apiKey", 186 | "protocol": "http", 187 | "host": [ 188 | "localhost" 189 | ], 190 | "port": "3000", 191 | "path": [ 192 | "cache", 193 | ":apiKey" 194 | ], 195 | "variable": [ 196 | { 197 | "key": "apiKey", 198 | "value": "TestKey001" 199 | } 200 | ] 201 | } 202 | }, 203 | "response": [] 204 | } 205 | ] 206 | } -------------------------------------------------------------------------------- /config/permitted-routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "GET" : ["*"], 3 | "POST": ["*/filter", "*/count"] 4 | } -------------------------------------------------------------------------------- /config/permitted-routes.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "http://softrams.io/schemas/prefect-auth-proxy-permitted-routes.json", 4 | "type": "object", 5 | "properties": {"$schema":true}, 6 | "patternProperties": { 7 | "(GET|POST|PUT|DELETE)": { 8 | "type": "array", 9 | "description": "HTTP METHOD with array of filter strings for permitted routes for the METHOD", 10 | "items": { 11 | "type": "string", 12 | "description": "Pattern to match against for the URL" 13 | }, 14 | "examples": [["*","*/filters"],["*/filters","*/count"]] 15 | } 16 | 17 | }, 18 | "additionalProperties": false 19 | } -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Running Database in a local container 2 | 3 | ## Postgres 4 | 5 | ```bash 6 | docker pull postgres 7 | 8 | docker run --name proxydb \ 9 | --mount type=bind,source=$(pwd)/data/postgres,target=/docker-entrypoint-initdb.d \ 10 | -p 5432:5432 \ 11 | -e POSTGRES_PASSWORD=`` \ 12 | -d postgres 13 | 14 | ``` 15 | 16 | ## MySQL 17 | 18 | ```bash 19 | docker pull mysql 20 | 21 | docker run --name proxydb \ 22 | --mount type=bind,source=$(pwd)/data/postgres,target=/docker-entrypoint-initdb.d \ 23 | -p 3306:3306 \ 24 | -e MYSQL_ROOT_PASSWORD=`` \ 25 | -d mysql 26 | 27 | ``` 28 | -------------------------------------------------------------------------------- /data/mysql/db_init.sql: -------------------------------------------------------------------------------- 1 | -- Create New Table 2 | CREATE TABLE `prefect_api_keys` ( 3 | `user_id` varchar(128) NOT NULL, 4 | `scopes` varchar(2000) NOT NULL, 5 | `api_key` varchar(128) NOT NULL, 6 | `key_issu_dt` datetime NOT NULL, 7 | `key_expr_dt` datetime NOT NULL, 8 | `creatd_dt` datetime NOT NULL, 9 | `last_updatd_dt` datetime NOT NULL, 10 | `creatd_by` varchar(11) NOT NULL, 11 | `last_updatd_by` varchar(11) NOT NULL, 12 | PRIMARY KEY (`user_id`) 13 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 14 | 15 | 16 | -- Sample Test Data 17 | insert into prefect_api_keys (user_id, scopes, api_key, key_issu_dt, key_expr_dt, creatd_dt, last_updatd_dt, creatd_by, last_updatd_by) 18 | values('USERID', 'mutation/*, query/*', 'TestKey001', '2021-12-27 00:00:00', '2022-12-31 00:00:00', '2021-12-30 00:00:00', '2021-12-30 00:00:00', 'ADMINUSERID', 'ADMINUSERID') 19 | -------------------------------------------------------------------------------- /data/postgres/db_init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE prefect_api_keys ( 2 | user_id varchar(128) NOT NULL, 3 | scopes varchar(2000) NOT NULL, 4 | api_key varchar(128) NOT NULL, 5 | key_issu_dt timestamp NOT NULL, 6 | key_expr_dt timestamp NOT NULL, 7 | creatd_dt timestamp NOT NULL, 8 | last_updatd_dt timestamp NOT NULL, 9 | creatd_by varchar(11) NOT NULL, 10 | last_updatd_by varchar(11) NOT NULL, 11 | PRIMARY KEY (user_id) 12 | ); 13 | 14 | insert into prefect_api_keys (user_id, scopes, api_key, key_issu_dt, key_expr_dt, creatd_dt, last_updatd_dt, creatd_by, last_updatd_by) 15 | values('USERID', 'mutation/*, query/*', 'TestKey001', '2021-12-27 00:00:00', '2022-12-31 00:00:00', '2021-12-30 00:00:00', '2021-12-30 00:00:00', 'ADMINUSERID', 'ADMINUSERID') 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const bodyParser = require("body-parser"); 3 | const { createProxyMiddleware } = require("http-proxy-middleware"); 4 | 5 | const mysql = require("mysql"); 6 | const Pool = require("pg-pool"); 7 | const fs = require("fs"); 8 | 9 | // Create Express Server 10 | const app = express(); 11 | 12 | const NodeCache = require("node-cache"); 13 | const { init } = require("express/lib/application"); 14 | 15 | // #region Initialization 16 | const tokenCache = new NodeCache({ 17 | stdTTL: 1800, // 30 minutes 18 | checkperiod: 300, // 5 minutes 19 | maxKeys: 100, 20 | deleteOnExpire: true, 21 | }); 22 | 23 | const config = { 24 | ALLOW_PUBLIC_ACCESS: process.env.ALLOW_PUBLIC_ACCESS || false, 25 | TENANT_ID: process.env.TENANT_ID || "Default", 26 | TENANT_NAME: process.env.TENANT_NAME || "Default", 27 | TENANT_SLUG: process.env.TENANT_SLUG || "default", 28 | LOG_LEVEL: process.env.LOG_LEVEL || "warn", 29 | API_SERVICE_URL: 30 | process.env.API_SERVICE_URL || "http://localhost:8080/graphql", 31 | HOST: process.env.HOST || "0.0.0.0", 32 | PORT: process.env.PORT || 3000, 33 | LOG_LEVEL_OVERRIDE_DURATION: process.env.LOG_LEVEL_OVERRIDE_DURATION || 300, 34 | ENV: process.env.ENV || "NA", 35 | PERMITTED_ROUTES_FILE: process.env.PERMITTED_ROUTES_FILE || "./config/permitted-routes.json", 36 | PERMITTED_ROUTES_JSON: process.env.PERMITTED_ROUTES_JSON || "" 37 | }; 38 | 39 | if (fs.existsSync(config.PERMITTED_ROUTES_FILE)) { 40 | if (config.PERMITTED_ROUTES_JSON) { 41 | config.PERMITTED_ROUTES = JSON.parse(config.PERMITTED_ROUTES_JSON) 42 | } 43 | else { 44 | config.PERMITTED_ROUTES = JSON.parse(fs.readFileSync(config.PERMITTED_ROUTES_FILE)); 45 | } 46 | } 47 | 48 | // #endregion 49 | 50 | // #region LOGGING 51 | // Setup Log Levels 52 | const gLogFunc = console.log; 53 | const gWarnFunc = console.warn; 54 | 55 | function initLogLevels(level) { 56 | console.debug = () => { }; 57 | console.trace = () => { }; 58 | console.info = () => { }; 59 | console.warn = () => { }; 60 | console.log = () => { }; 61 | 62 | if (level === "warn") { 63 | console.log = () => { }; 64 | console.warn = gWarnFunc; 65 | } 66 | if (level === "error") { 67 | console.log = () => { }; 68 | console.warn = () => { }; 69 | } 70 | if (level === "info") { 71 | console.log = gLogFunc; 72 | console.warn = gWarnFunc; 73 | } 74 | gLogFunc("Log Level set to ", level); 75 | } 76 | 77 | initLogLevels(config.LOG_LEVEL); 78 | gLogFunc("Service running with configuration:", config); 79 | 80 | // #endregion 81 | 82 | // #region Public Endpoints 83 | // Public Endpoints 84 | app.get("/healthcheck", (req, res, next) => { 85 | res.send("All OK"); 86 | }); 87 | 88 | // Allow enabling verbose logging for debugging 89 | app.post("/logs/:level", (req, res, next) => { 90 | initLogLevels(req.params.level); 91 | setTimeout(() => { 92 | initLogLevels(config.LOG_LEVEL); 93 | }, config.LOG_LEVEL_OVERRIDE_DURATION * 1000); 94 | res.send(`Log Level Changed to ${req.params.level}`); 95 | }); 96 | 97 | // Allow cache reset for testing 98 | app.post("/cache/reset", (req, res, next) => { 99 | tokenCache.flushAll(); 100 | res.send(`Cache Reset`); 101 | }); 102 | 103 | app.delete("/cache/:key", (req, res, next) => { 104 | tokenCache.del(req.params.key); 105 | res.send(`Cache has been deleted for key: ${req.params.key}`); 106 | }); 107 | 108 | // #endregion 109 | 110 | // #region Utilities 111 | 112 | let dbConn; 113 | 114 | async function initDBConnection() { 115 | return new Promise(async (resolve, reject) => { 116 | try { 117 | if (process.env.DB_TYPE === "mysql") { 118 | dbConn = mysql.createPool({ 119 | connectionLimit: 5, 120 | host: process.env.DB_HOST, 121 | user: process.env.DB_USER, 122 | password: process.env.DB_PASSWORD, 123 | database: process.env.DB_DATABASE, 124 | }); 125 | } else { 126 | dbConn = new Pool({ 127 | user: process.env.DB_USER, 128 | host: process.env.DB_HOST, 129 | database: process.env.DB_DATABASE, 130 | password: process.env.DB_PASSWORD, 131 | port: 5432, 132 | }); 133 | } 134 | resolve(true); 135 | } catch (err) { 136 | console.log("Failure in creating DB connection pool:", err); 137 | reject(err); 138 | } 139 | }); 140 | } 141 | 142 | async function dbQuery(query, params) { 143 | return new Promise(async (resolve, reject) => { 144 | try { 145 | dbConn.query(query, params, (error, results) => { 146 | if (error) { 147 | reject(error); 148 | } else { 149 | // console.log('dbQuery Results:', results); 150 | resolve(results.rows ? results.rows : results); 151 | } 152 | }); 153 | } catch (err) { 154 | console.log("Failure in query: ", err); 155 | reject(err); 156 | } 157 | }); 158 | } 159 | 160 | async function fetchAPIKeysInfo(key) { 161 | return new Promise(async (resolve, reject) => { 162 | try { 163 | let query = ""; 164 | let params; 165 | if (process.env.DB_TYPE === "mysql") { 166 | params = [key]; 167 | query = 168 | "SELECT * FROM prefect_api_keys WHERE api_key=? AND CURDATE() < key_expr_dt"; 169 | } else { 170 | params = [key]; 171 | query = 172 | "SELECT * FROM prefect_api_keys WHERE api_key=$1 AND CURRENT_TIMESTAMP < key_expr_dt"; 173 | } 174 | 175 | dbConn.query(query, params, (error, results) => { 176 | if (error) { 177 | reject(error); 178 | } else { 179 | // console.log('dbQuery Results:', results); 180 | resolve(results.rows ? results.rows : results); 181 | } 182 | }); 183 | } catch (err) { 184 | console.log("Failure in query: ", err); 185 | reject(err); 186 | } 187 | }); 188 | } 189 | /** 190 | * 191 | * @param {String} url "Url to validate against route rules" 192 | * @param {Array} routes "Allowed or Permitted routes to match against" 193 | * @returns 194 | */ 195 | const checkRoutes = (url, routes) => { 196 | if (!routes || !routes.length) { 197 | return false; 198 | } 199 | 200 | regexRoutes = routes.map(route => route.replace(/\*/g, "[^ ]*")); 201 | 202 | for (let i = 0; i < regexRoutes.length; i++) { 203 | const match = url.match(regexRoutes[i]); 204 | 205 | if (match) { 206 | return true; 207 | } 208 | } 209 | 210 | return false; 211 | }; 212 | 213 | const allowPassthrough = (url, method, acl) => { 214 | // check config for public access 215 | if (config.ALLOW_PUBLIC_ACCESS) { 216 | return true; 217 | } 218 | // check permitted routes 219 | if (config.PERMITTED_ROUTES && config.PERMITTED_ROUTES[method]?.length && checkRoutes(url, config.PERMITTED_ROUTES[method])) { 220 | return true; 221 | } 222 | 223 | // check acl 224 | if (checkRoutes(url, acl?.ops)) { 225 | return true; 226 | } 227 | return false; 228 | }; 229 | // #endregion 230 | 231 | // #region Middleware 232 | // Body Parser Middleware 233 | app.use(bodyParser.json()); 234 | app.use(bodyParser.urlencoded({ extended: true })); 235 | 236 | // Auth Middleware 237 | app.use(async (req, res, next) => { 238 | let apiKey = null; 239 | 240 | const authHeader = req.headers.authorization; 241 | if (authHeader) { 242 | apiKey = authHeader.split(" ")[1]; 243 | } else { 244 | apiKey = req.get("x-prefect-user-id"); 245 | } 246 | 247 | if (apiKey) { 248 | req.apikey = apiKey; 249 | let acl = tokenCache.get(apiKey); 250 | if (!acl) { 251 | // Fetch ACL from database and store in cache 252 | const aclDB = await fetchAPIKeysInfo(apiKey); 253 | if (aclDB && aclDB.length > 0) { 254 | acl = aclDB[0]; 255 | acl.ops = acl.scopes.split(",").map((el) => el.trim()); 256 | console.log("ACL: ", acl); 257 | tokenCache.set(apiKey, acl); 258 | } else { 259 | acl = { 260 | user_id: "Anonymous", 261 | ops: [], 262 | }; 263 | } 264 | } 265 | console.log("ACL: ", acl); 266 | req.acl = acl; 267 | } 268 | 269 | if (!allowPassthrough(req.url, req.method, req.acl)) { 270 | console.warn(`AUTH-PROXY-AUDIT: ${req.url}`) 271 | return res.status(401).send("Unauthorized"); 272 | } else { 273 | next(); 274 | } 275 | }); 276 | 277 | // #endregion 278 | 279 | // #region Proxy Setup 280 | app.use( 281 | "/", 282 | createProxyMiddleware({ 283 | target: config.API_SERVICE_URL, 284 | changeOrigin: true, 285 | logLevel: "error", 286 | // pathRewrite: { 287 | // [`^/graphql`]: "/graphql", 288 | // }, 289 | onProxyRes: (proxyRes, req, res) => { 290 | console.log("Response from API: ", proxyRes.statusCode); 291 | console.log("Response Headers", proxyRes.headers); 292 | var body = []; 293 | proxyRes.on("data", function (chunk) { 294 | body.push(chunk); 295 | }); 296 | proxyRes.on("end", function () { 297 | body = Buffer.concat(body).toString(); 298 | console.log("res from proxied server:", body); 299 | // res.end("my response to cli"); 300 | }); 301 | }, 302 | onProxyReq: (proxyReq, req, res) => { 303 | console.log("URL", req.url); 304 | console.log("Headers:", req.headers); 305 | console.log("Body:", req.body); 306 | 307 | if ( 308 | req.url === "/" && 309 | req.body && 310 | req.body.query && 311 | req.body.query.includes("auth_info") 312 | ) { 313 | res.json({ 314 | data: { 315 | auth_info: { tenant_id: config.TENANT_ID }, 316 | }, 317 | }); 318 | } else if ( 319 | req.url === "/" && 320 | req.body && 321 | req.body.query && 322 | req.body.query.includes("tenant(where") 323 | ) { 324 | res.json({ 325 | data: { 326 | tenant: [ 327 | { 328 | name: config.TENANT_NAME, 329 | slug: config.TENANT_SLUG, 330 | id: config.TENANT_ID, 331 | }, 332 | ], 333 | }, 334 | }); 335 | } else { 336 | // Proxy request to end point 337 | const writeBody = (bodyData) => { 338 | proxyReq.setHeader("Content-Length", Buffer.byteLength(bodyData)); 339 | if (config.LOG_LEVEL === "debug") { 340 | proxyReq.removeHeader('Accept-Encoding') 341 | } 342 | proxyReq.write(bodyData); 343 | }; 344 | 345 | if (req.body) { 346 | writeBody(JSON.stringify(req.body)); 347 | } 348 | } 349 | }, 350 | }) 351 | ); 352 | 353 | // #endregion 354 | 355 | // Start the Proxy 356 | app.listen(config.PORT, config.HOST, async () => { 357 | gLogFunc(`Starting Proxy at ${config.HOST}:${config.PORT}`); 358 | if (!dbConn) { 359 | await initDBConnection(); 360 | } 361 | }); 362 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prefect-auth-proxy", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "prefect-auth-proxy", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "body-parser": "^1.20.2", 13 | "express": "^4.18.2", 14 | "http-proxy-middleware": "^2.0.6", 15 | "mysql": "^2.18.1", 16 | "node-cache": "^5.1.2", 17 | "pg": "^8.11.3", 18 | "pg-pool": "^3.6.1" 19 | }, 20 | "engines": { 21 | "node": ">=16" 22 | } 23 | }, 24 | "node_modules/@types/http-proxy": { 25 | "version": "1.17.14", 26 | "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", 27 | "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", 28 | "dependencies": { 29 | "@types/node": "*" 30 | } 31 | }, 32 | "node_modules/@types/node": { 33 | "version": "20.14.10", 34 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", 35 | "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", 36 | "dependencies": { 37 | "undici-types": "~5.26.4" 38 | } 39 | }, 40 | "node_modules/accepts": { 41 | "version": "1.3.8", 42 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 43 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 44 | "dependencies": { 45 | "mime-types": "~2.1.34", 46 | "negotiator": "0.6.3" 47 | }, 48 | "engines": { 49 | "node": ">= 0.6" 50 | } 51 | }, 52 | "node_modules/array-flatten": { 53 | "version": "1.1.1", 54 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 55 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 56 | }, 57 | "node_modules/bignumber.js": { 58 | "version": "9.0.0", 59 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", 60 | "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", 61 | "engines": { 62 | "node": "*" 63 | } 64 | }, 65 | "node_modules/body-parser": { 66 | "version": "1.20.2", 67 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", 68 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", 69 | "dependencies": { 70 | "bytes": "3.1.2", 71 | "content-type": "~1.0.5", 72 | "debug": "2.6.9", 73 | "depd": "2.0.0", 74 | "destroy": "1.2.0", 75 | "http-errors": "2.0.0", 76 | "iconv-lite": "0.4.24", 77 | "on-finished": "2.4.1", 78 | "qs": "6.11.0", 79 | "raw-body": "2.5.2", 80 | "type-is": "~1.6.18", 81 | "unpipe": "1.0.0" 82 | }, 83 | "engines": { 84 | "node": ">= 0.8", 85 | "npm": "1.2.8000 || >= 1.4.16" 86 | } 87 | }, 88 | "node_modules/braces": { 89 | "version": "3.0.3", 90 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 91 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 92 | "dependencies": { 93 | "fill-range": "^7.1.1" 94 | }, 95 | "engines": { 96 | "node": ">=8" 97 | } 98 | }, 99 | "node_modules/bytes": { 100 | "version": "3.1.2", 101 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 102 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 103 | "engines": { 104 | "node": ">= 0.8" 105 | } 106 | }, 107 | "node_modules/call-bind": { 108 | "version": "1.0.7", 109 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 110 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 111 | "dependencies": { 112 | "es-define-property": "^1.0.0", 113 | "es-errors": "^1.3.0", 114 | "function-bind": "^1.1.2", 115 | "get-intrinsic": "^1.2.4", 116 | "set-function-length": "^1.2.1" 117 | }, 118 | "engines": { 119 | "node": ">= 0.4" 120 | }, 121 | "funding": { 122 | "url": "https://github.com/sponsors/ljharb" 123 | } 124 | }, 125 | "node_modules/clone": { 126 | "version": "2.1.2", 127 | "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", 128 | "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", 129 | "engines": { 130 | "node": ">=0.8" 131 | } 132 | }, 133 | "node_modules/content-disposition": { 134 | "version": "0.5.4", 135 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 136 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 137 | "dependencies": { 138 | "safe-buffer": "5.2.1" 139 | }, 140 | "engines": { 141 | "node": ">= 0.6" 142 | } 143 | }, 144 | "node_modules/content-type": { 145 | "version": "1.0.5", 146 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 147 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 148 | "engines": { 149 | "node": ">= 0.6" 150 | } 151 | }, 152 | "node_modules/cookie": { 153 | "version": "0.6.0", 154 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", 155 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", 156 | "engines": { 157 | "node": ">= 0.6" 158 | } 159 | }, 160 | "node_modules/cookie-signature": { 161 | "version": "1.0.6", 162 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 163 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 164 | }, 165 | "node_modules/core-util-is": { 166 | "version": "1.0.3", 167 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 168 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 169 | }, 170 | "node_modules/debug": { 171 | "version": "2.6.9", 172 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 173 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 174 | "dependencies": { 175 | "ms": "2.0.0" 176 | } 177 | }, 178 | "node_modules/define-data-property": { 179 | "version": "1.1.4", 180 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 181 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 182 | "dependencies": { 183 | "es-define-property": "^1.0.0", 184 | "es-errors": "^1.3.0", 185 | "gopd": "^1.0.1" 186 | }, 187 | "engines": { 188 | "node": ">= 0.4" 189 | }, 190 | "funding": { 191 | "url": "https://github.com/sponsors/ljharb" 192 | } 193 | }, 194 | "node_modules/depd": { 195 | "version": "2.0.0", 196 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 197 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 198 | "engines": { 199 | "node": ">= 0.8" 200 | } 201 | }, 202 | "node_modules/destroy": { 203 | "version": "1.2.0", 204 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 205 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 206 | "engines": { 207 | "node": ">= 0.8", 208 | "npm": "1.2.8000 || >= 1.4.16" 209 | } 210 | }, 211 | "node_modules/ee-first": { 212 | "version": "1.1.1", 213 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 214 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 215 | }, 216 | "node_modules/encodeurl": { 217 | "version": "1.0.2", 218 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 219 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 220 | "engines": { 221 | "node": ">= 0.8" 222 | } 223 | }, 224 | "node_modules/es-define-property": { 225 | "version": "1.0.0", 226 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 227 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 228 | "dependencies": { 229 | "get-intrinsic": "^1.2.4" 230 | }, 231 | "engines": { 232 | "node": ">= 0.4" 233 | } 234 | }, 235 | "node_modules/es-errors": { 236 | "version": "1.3.0", 237 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 238 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 239 | "engines": { 240 | "node": ">= 0.4" 241 | } 242 | }, 243 | "node_modules/escape-html": { 244 | "version": "1.0.3", 245 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 246 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 247 | }, 248 | "node_modules/etag": { 249 | "version": "1.8.1", 250 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 251 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 252 | "engines": { 253 | "node": ">= 0.6" 254 | } 255 | }, 256 | "node_modules/eventemitter3": { 257 | "version": "4.0.7", 258 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 259 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" 260 | }, 261 | "node_modules/express": { 262 | "version": "4.19.2", 263 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", 264 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", 265 | "dependencies": { 266 | "accepts": "~1.3.8", 267 | "array-flatten": "1.1.1", 268 | "body-parser": "1.20.2", 269 | "content-disposition": "0.5.4", 270 | "content-type": "~1.0.4", 271 | "cookie": "0.6.0", 272 | "cookie-signature": "1.0.6", 273 | "debug": "2.6.9", 274 | "depd": "2.0.0", 275 | "encodeurl": "~1.0.2", 276 | "escape-html": "~1.0.3", 277 | "etag": "~1.8.1", 278 | "finalhandler": "1.2.0", 279 | "fresh": "0.5.2", 280 | "http-errors": "2.0.0", 281 | "merge-descriptors": "1.0.1", 282 | "methods": "~1.1.2", 283 | "on-finished": "2.4.1", 284 | "parseurl": "~1.3.3", 285 | "path-to-regexp": "0.1.7", 286 | "proxy-addr": "~2.0.7", 287 | "qs": "6.11.0", 288 | "range-parser": "~1.2.1", 289 | "safe-buffer": "5.2.1", 290 | "send": "0.18.0", 291 | "serve-static": "1.15.0", 292 | "setprototypeof": "1.2.0", 293 | "statuses": "2.0.1", 294 | "type-is": "~1.6.18", 295 | "utils-merge": "1.0.1", 296 | "vary": "~1.1.2" 297 | }, 298 | "engines": { 299 | "node": ">= 0.10.0" 300 | } 301 | }, 302 | "node_modules/fill-range": { 303 | "version": "7.1.1", 304 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 305 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 306 | "dependencies": { 307 | "to-regex-range": "^5.0.1" 308 | }, 309 | "engines": { 310 | "node": ">=8" 311 | } 312 | }, 313 | "node_modules/finalhandler": { 314 | "version": "1.2.0", 315 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 316 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 317 | "dependencies": { 318 | "debug": "2.6.9", 319 | "encodeurl": "~1.0.2", 320 | "escape-html": "~1.0.3", 321 | "on-finished": "2.4.1", 322 | "parseurl": "~1.3.3", 323 | "statuses": "2.0.1", 324 | "unpipe": "~1.0.0" 325 | }, 326 | "engines": { 327 | "node": ">= 0.8" 328 | } 329 | }, 330 | "node_modules/follow-redirects": { 331 | "version": "1.15.6", 332 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 333 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 334 | "funding": [ 335 | { 336 | "type": "individual", 337 | "url": "https://github.com/sponsors/RubenVerborgh" 338 | } 339 | ], 340 | "engines": { 341 | "node": ">=4.0" 342 | }, 343 | "peerDependenciesMeta": { 344 | "debug": { 345 | "optional": true 346 | } 347 | } 348 | }, 349 | "node_modules/forwarded": { 350 | "version": "0.2.0", 351 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 352 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 353 | "engines": { 354 | "node": ">= 0.6" 355 | } 356 | }, 357 | "node_modules/fresh": { 358 | "version": "0.5.2", 359 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 360 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 361 | "engines": { 362 | "node": ">= 0.6" 363 | } 364 | }, 365 | "node_modules/function-bind": { 366 | "version": "1.1.2", 367 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 368 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 369 | "funding": { 370 | "url": "https://github.com/sponsors/ljharb" 371 | } 372 | }, 373 | "node_modules/get-intrinsic": { 374 | "version": "1.2.4", 375 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 376 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 377 | "dependencies": { 378 | "es-errors": "^1.3.0", 379 | "function-bind": "^1.1.2", 380 | "has-proto": "^1.0.1", 381 | "has-symbols": "^1.0.3", 382 | "hasown": "^2.0.0" 383 | }, 384 | "engines": { 385 | "node": ">= 0.4" 386 | }, 387 | "funding": { 388 | "url": "https://github.com/sponsors/ljharb" 389 | } 390 | }, 391 | "node_modules/gopd": { 392 | "version": "1.0.1", 393 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 394 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 395 | "dependencies": { 396 | "get-intrinsic": "^1.1.3" 397 | }, 398 | "funding": { 399 | "url": "https://github.com/sponsors/ljharb" 400 | } 401 | }, 402 | "node_modules/has-property-descriptors": { 403 | "version": "1.0.2", 404 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 405 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 406 | "dependencies": { 407 | "es-define-property": "^1.0.0" 408 | }, 409 | "funding": { 410 | "url": "https://github.com/sponsors/ljharb" 411 | } 412 | }, 413 | "node_modules/has-proto": { 414 | "version": "1.0.3", 415 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 416 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 417 | "engines": { 418 | "node": ">= 0.4" 419 | }, 420 | "funding": { 421 | "url": "https://github.com/sponsors/ljharb" 422 | } 423 | }, 424 | "node_modules/has-symbols": { 425 | "version": "1.0.3", 426 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 427 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 428 | "engines": { 429 | "node": ">= 0.4" 430 | }, 431 | "funding": { 432 | "url": "https://github.com/sponsors/ljharb" 433 | } 434 | }, 435 | "node_modules/hasown": { 436 | "version": "2.0.2", 437 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 438 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 439 | "dependencies": { 440 | "function-bind": "^1.1.2" 441 | }, 442 | "engines": { 443 | "node": ">= 0.4" 444 | } 445 | }, 446 | "node_modules/http-errors": { 447 | "version": "2.0.0", 448 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 449 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 450 | "dependencies": { 451 | "depd": "2.0.0", 452 | "inherits": "2.0.4", 453 | "setprototypeof": "1.2.0", 454 | "statuses": "2.0.1", 455 | "toidentifier": "1.0.1" 456 | }, 457 | "engines": { 458 | "node": ">= 0.8" 459 | } 460 | }, 461 | "node_modules/http-proxy": { 462 | "version": "1.18.1", 463 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 464 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 465 | "dependencies": { 466 | "eventemitter3": "^4.0.0", 467 | "follow-redirects": "^1.0.0", 468 | "requires-port": "^1.0.0" 469 | }, 470 | "engines": { 471 | "node": ">=8.0.0" 472 | } 473 | }, 474 | "node_modules/http-proxy-middleware": { 475 | "version": "2.0.6", 476 | "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", 477 | "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", 478 | "dependencies": { 479 | "@types/http-proxy": "^1.17.8", 480 | "http-proxy": "^1.18.1", 481 | "is-glob": "^4.0.1", 482 | "is-plain-obj": "^3.0.0", 483 | "micromatch": "^4.0.2" 484 | }, 485 | "engines": { 486 | "node": ">=12.0.0" 487 | }, 488 | "peerDependencies": { 489 | "@types/express": "^4.17.13" 490 | }, 491 | "peerDependenciesMeta": { 492 | "@types/express": { 493 | "optional": true 494 | } 495 | } 496 | }, 497 | "node_modules/iconv-lite": { 498 | "version": "0.4.24", 499 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 500 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 501 | "dependencies": { 502 | "safer-buffer": ">= 2.1.2 < 3" 503 | }, 504 | "engines": { 505 | "node": ">=0.10.0" 506 | } 507 | }, 508 | "node_modules/inherits": { 509 | "version": "2.0.4", 510 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 511 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 512 | }, 513 | "node_modules/ipaddr.js": { 514 | "version": "1.9.1", 515 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 516 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 517 | "engines": { 518 | "node": ">= 0.10" 519 | } 520 | }, 521 | "node_modules/is-extglob": { 522 | "version": "2.1.1", 523 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 524 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 525 | "engines": { 526 | "node": ">=0.10.0" 527 | } 528 | }, 529 | "node_modules/is-glob": { 530 | "version": "4.0.3", 531 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 532 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 533 | "dependencies": { 534 | "is-extglob": "^2.1.1" 535 | }, 536 | "engines": { 537 | "node": ">=0.10.0" 538 | } 539 | }, 540 | "node_modules/is-number": { 541 | "version": "7.0.0", 542 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 543 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 544 | "engines": { 545 | "node": ">=0.12.0" 546 | } 547 | }, 548 | "node_modules/is-plain-obj": { 549 | "version": "3.0.0", 550 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", 551 | "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", 552 | "engines": { 553 | "node": ">=10" 554 | }, 555 | "funding": { 556 | "url": "https://github.com/sponsors/sindresorhus" 557 | } 558 | }, 559 | "node_modules/isarray": { 560 | "version": "1.0.0", 561 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 562 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 563 | }, 564 | "node_modules/media-typer": { 565 | "version": "0.3.0", 566 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 567 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 568 | "engines": { 569 | "node": ">= 0.6" 570 | } 571 | }, 572 | "node_modules/merge-descriptors": { 573 | "version": "1.0.1", 574 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 575 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 576 | }, 577 | "node_modules/methods": { 578 | "version": "1.1.2", 579 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 580 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 581 | "engines": { 582 | "node": ">= 0.6" 583 | } 584 | }, 585 | "node_modules/micromatch": { 586 | "version": "4.0.7", 587 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", 588 | "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", 589 | "dependencies": { 590 | "braces": "^3.0.3", 591 | "picomatch": "^2.3.1" 592 | }, 593 | "engines": { 594 | "node": ">=8.6" 595 | } 596 | }, 597 | "node_modules/mime": { 598 | "version": "1.6.0", 599 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 600 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 601 | "bin": { 602 | "mime": "cli.js" 603 | }, 604 | "engines": { 605 | "node": ">=4" 606 | } 607 | }, 608 | "node_modules/mime-db": { 609 | "version": "1.52.0", 610 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 611 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 612 | "engines": { 613 | "node": ">= 0.6" 614 | } 615 | }, 616 | "node_modules/mime-types": { 617 | "version": "2.1.35", 618 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 619 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 620 | "dependencies": { 621 | "mime-db": "1.52.0" 622 | }, 623 | "engines": { 624 | "node": ">= 0.6" 625 | } 626 | }, 627 | "node_modules/ms": { 628 | "version": "2.0.0", 629 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 630 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 631 | }, 632 | "node_modules/mysql": { 633 | "version": "2.18.1", 634 | "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", 635 | "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", 636 | "dependencies": { 637 | "bignumber.js": "9.0.0", 638 | "readable-stream": "2.3.7", 639 | "safe-buffer": "5.1.2", 640 | "sqlstring": "2.3.1" 641 | }, 642 | "engines": { 643 | "node": ">= 0.6" 644 | } 645 | }, 646 | "node_modules/mysql/node_modules/safe-buffer": { 647 | "version": "5.1.2", 648 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 649 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 650 | }, 651 | "node_modules/negotiator": { 652 | "version": "0.6.3", 653 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 654 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 655 | "engines": { 656 | "node": ">= 0.6" 657 | } 658 | }, 659 | "node_modules/node-cache": { 660 | "version": "5.1.2", 661 | "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", 662 | "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", 663 | "dependencies": { 664 | "clone": "2.x" 665 | }, 666 | "engines": { 667 | "node": ">= 8.0.0" 668 | } 669 | }, 670 | "node_modules/object-inspect": { 671 | "version": "1.13.2", 672 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", 673 | "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", 674 | "engines": { 675 | "node": ">= 0.4" 676 | }, 677 | "funding": { 678 | "url": "https://github.com/sponsors/ljharb" 679 | } 680 | }, 681 | "node_modules/on-finished": { 682 | "version": "2.4.1", 683 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 684 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 685 | "dependencies": { 686 | "ee-first": "1.1.1" 687 | }, 688 | "engines": { 689 | "node": ">= 0.8" 690 | } 691 | }, 692 | "node_modules/parseurl": { 693 | "version": "1.3.3", 694 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 695 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 696 | "engines": { 697 | "node": ">= 0.8" 698 | } 699 | }, 700 | "node_modules/path-to-regexp": { 701 | "version": "0.1.7", 702 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 703 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 704 | }, 705 | "node_modules/pg": { 706 | "version": "8.12.0", 707 | "resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", 708 | "integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==", 709 | "dependencies": { 710 | "pg-connection-string": "^2.6.4", 711 | "pg-pool": "^3.6.2", 712 | "pg-protocol": "^1.6.1", 713 | "pg-types": "^2.1.0", 714 | "pgpass": "1.x" 715 | }, 716 | "engines": { 717 | "node": ">= 8.0.0" 718 | }, 719 | "optionalDependencies": { 720 | "pg-cloudflare": "^1.1.1" 721 | }, 722 | "peerDependencies": { 723 | "pg-native": ">=3.0.1" 724 | }, 725 | "peerDependenciesMeta": { 726 | "pg-native": { 727 | "optional": true 728 | } 729 | } 730 | }, 731 | "node_modules/pg-cloudflare": { 732 | "version": "1.1.1", 733 | "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", 734 | "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", 735 | "optional": true 736 | }, 737 | "node_modules/pg-connection-string": { 738 | "version": "2.6.4", 739 | "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", 740 | "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==" 741 | }, 742 | "node_modules/pg-int8": { 743 | "version": "1.0.1", 744 | "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", 745 | "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", 746 | "engines": { 747 | "node": ">=4.0.0" 748 | } 749 | }, 750 | "node_modules/pg-pool": { 751 | "version": "3.6.2", 752 | "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", 753 | "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", 754 | "peerDependencies": { 755 | "pg": ">=8.0" 756 | } 757 | }, 758 | "node_modules/pg-protocol": { 759 | "version": "1.6.1", 760 | "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", 761 | "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==" 762 | }, 763 | "node_modules/pg-types": { 764 | "version": "2.2.0", 765 | "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", 766 | "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", 767 | "dependencies": { 768 | "pg-int8": "1.0.1", 769 | "postgres-array": "~2.0.0", 770 | "postgres-bytea": "~1.0.0", 771 | "postgres-date": "~1.0.4", 772 | "postgres-interval": "^1.1.0" 773 | }, 774 | "engines": { 775 | "node": ">=4" 776 | } 777 | }, 778 | "node_modules/pgpass": { 779 | "version": "1.0.5", 780 | "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", 781 | "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", 782 | "dependencies": { 783 | "split2": "^4.1.0" 784 | } 785 | }, 786 | "node_modules/picomatch": { 787 | "version": "2.3.1", 788 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 789 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 790 | "engines": { 791 | "node": ">=8.6" 792 | }, 793 | "funding": { 794 | "url": "https://github.com/sponsors/jonschlinkert" 795 | } 796 | }, 797 | "node_modules/postgres-array": { 798 | "version": "2.0.0", 799 | "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", 800 | "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", 801 | "engines": { 802 | "node": ">=4" 803 | } 804 | }, 805 | "node_modules/postgres-bytea": { 806 | "version": "1.0.0", 807 | "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", 808 | "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", 809 | "engines": { 810 | "node": ">=0.10.0" 811 | } 812 | }, 813 | "node_modules/postgres-date": { 814 | "version": "1.0.7", 815 | "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", 816 | "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", 817 | "engines": { 818 | "node": ">=0.10.0" 819 | } 820 | }, 821 | "node_modules/postgres-interval": { 822 | "version": "1.2.0", 823 | "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", 824 | "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", 825 | "dependencies": { 826 | "xtend": "^4.0.0" 827 | }, 828 | "engines": { 829 | "node": ">=0.10.0" 830 | } 831 | }, 832 | "node_modules/process-nextick-args": { 833 | "version": "2.0.1", 834 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 835 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 836 | }, 837 | "node_modules/proxy-addr": { 838 | "version": "2.0.7", 839 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 840 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 841 | "dependencies": { 842 | "forwarded": "0.2.0", 843 | "ipaddr.js": "1.9.1" 844 | }, 845 | "engines": { 846 | "node": ">= 0.10" 847 | } 848 | }, 849 | "node_modules/qs": { 850 | "version": "6.11.0", 851 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 852 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 853 | "dependencies": { 854 | "side-channel": "^1.0.4" 855 | }, 856 | "engines": { 857 | "node": ">=0.6" 858 | }, 859 | "funding": { 860 | "url": "https://github.com/sponsors/ljharb" 861 | } 862 | }, 863 | "node_modules/range-parser": { 864 | "version": "1.2.1", 865 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 866 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 867 | "engines": { 868 | "node": ">= 0.6" 869 | } 870 | }, 871 | "node_modules/raw-body": { 872 | "version": "2.5.2", 873 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 874 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 875 | "dependencies": { 876 | "bytes": "3.1.2", 877 | "http-errors": "2.0.0", 878 | "iconv-lite": "0.4.24", 879 | "unpipe": "1.0.0" 880 | }, 881 | "engines": { 882 | "node": ">= 0.8" 883 | } 884 | }, 885 | "node_modules/readable-stream": { 886 | "version": "2.3.7", 887 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 888 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 889 | "dependencies": { 890 | "core-util-is": "~1.0.0", 891 | "inherits": "~2.0.3", 892 | "isarray": "~1.0.0", 893 | "process-nextick-args": "~2.0.0", 894 | "safe-buffer": "~5.1.1", 895 | "string_decoder": "~1.1.1", 896 | "util-deprecate": "~1.0.1" 897 | } 898 | }, 899 | "node_modules/readable-stream/node_modules/safe-buffer": { 900 | "version": "5.1.2", 901 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 902 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 903 | }, 904 | "node_modules/requires-port": { 905 | "version": "1.0.0", 906 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 907 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" 908 | }, 909 | "node_modules/safe-buffer": { 910 | "version": "5.2.1", 911 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 912 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 913 | "funding": [ 914 | { 915 | "type": "github", 916 | "url": "https://github.com/sponsors/feross" 917 | }, 918 | { 919 | "type": "patreon", 920 | "url": "https://www.patreon.com/feross" 921 | }, 922 | { 923 | "type": "consulting", 924 | "url": "https://feross.org/support" 925 | } 926 | ] 927 | }, 928 | "node_modules/safer-buffer": { 929 | "version": "2.1.2", 930 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 931 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 932 | }, 933 | "node_modules/send": { 934 | "version": "0.18.0", 935 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 936 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 937 | "dependencies": { 938 | "debug": "2.6.9", 939 | "depd": "2.0.0", 940 | "destroy": "1.2.0", 941 | "encodeurl": "~1.0.2", 942 | "escape-html": "~1.0.3", 943 | "etag": "~1.8.1", 944 | "fresh": "0.5.2", 945 | "http-errors": "2.0.0", 946 | "mime": "1.6.0", 947 | "ms": "2.1.3", 948 | "on-finished": "2.4.1", 949 | "range-parser": "~1.2.1", 950 | "statuses": "2.0.1" 951 | }, 952 | "engines": { 953 | "node": ">= 0.8.0" 954 | } 955 | }, 956 | "node_modules/send/node_modules/ms": { 957 | "version": "2.1.3", 958 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 959 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 960 | }, 961 | "node_modules/serve-static": { 962 | "version": "1.15.0", 963 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 964 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 965 | "dependencies": { 966 | "encodeurl": "~1.0.2", 967 | "escape-html": "~1.0.3", 968 | "parseurl": "~1.3.3", 969 | "send": "0.18.0" 970 | }, 971 | "engines": { 972 | "node": ">= 0.8.0" 973 | } 974 | }, 975 | "node_modules/set-function-length": { 976 | "version": "1.2.2", 977 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 978 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 979 | "dependencies": { 980 | "define-data-property": "^1.1.4", 981 | "es-errors": "^1.3.0", 982 | "function-bind": "^1.1.2", 983 | "get-intrinsic": "^1.2.4", 984 | "gopd": "^1.0.1", 985 | "has-property-descriptors": "^1.0.2" 986 | }, 987 | "engines": { 988 | "node": ">= 0.4" 989 | } 990 | }, 991 | "node_modules/setprototypeof": { 992 | "version": "1.2.0", 993 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 994 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 995 | }, 996 | "node_modules/side-channel": { 997 | "version": "1.0.6", 998 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 999 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 1000 | "dependencies": { 1001 | "call-bind": "^1.0.7", 1002 | "es-errors": "^1.3.0", 1003 | "get-intrinsic": "^1.2.4", 1004 | "object-inspect": "^1.13.1" 1005 | }, 1006 | "engines": { 1007 | "node": ">= 0.4" 1008 | }, 1009 | "funding": { 1010 | "url": "https://github.com/sponsors/ljharb" 1011 | } 1012 | }, 1013 | "node_modules/split2": { 1014 | "version": "4.2.0", 1015 | "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", 1016 | "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", 1017 | "engines": { 1018 | "node": ">= 10.x" 1019 | } 1020 | }, 1021 | "node_modules/sqlstring": { 1022 | "version": "2.3.1", 1023 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", 1024 | "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==", 1025 | "engines": { 1026 | "node": ">= 0.6" 1027 | } 1028 | }, 1029 | "node_modules/statuses": { 1030 | "version": "2.0.1", 1031 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1032 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1033 | "engines": { 1034 | "node": ">= 0.8" 1035 | } 1036 | }, 1037 | "node_modules/string_decoder": { 1038 | "version": "1.1.1", 1039 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1040 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1041 | "dependencies": { 1042 | "safe-buffer": "~5.1.0" 1043 | } 1044 | }, 1045 | "node_modules/string_decoder/node_modules/safe-buffer": { 1046 | "version": "5.1.2", 1047 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1048 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1049 | }, 1050 | "node_modules/to-regex-range": { 1051 | "version": "5.0.1", 1052 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1053 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1054 | "dependencies": { 1055 | "is-number": "^7.0.0" 1056 | }, 1057 | "engines": { 1058 | "node": ">=8.0" 1059 | } 1060 | }, 1061 | "node_modules/toidentifier": { 1062 | "version": "1.0.1", 1063 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1064 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1065 | "engines": { 1066 | "node": ">=0.6" 1067 | } 1068 | }, 1069 | "node_modules/type-is": { 1070 | "version": "1.6.18", 1071 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1072 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1073 | "dependencies": { 1074 | "media-typer": "0.3.0", 1075 | "mime-types": "~2.1.24" 1076 | }, 1077 | "engines": { 1078 | "node": ">= 0.6" 1079 | } 1080 | }, 1081 | "node_modules/undici-types": { 1082 | "version": "5.26.5", 1083 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 1084 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" 1085 | }, 1086 | "node_modules/unpipe": { 1087 | "version": "1.0.0", 1088 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1089 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1090 | "engines": { 1091 | "node": ">= 0.8" 1092 | } 1093 | }, 1094 | "node_modules/util-deprecate": { 1095 | "version": "1.0.2", 1096 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1097 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 1098 | }, 1099 | "node_modules/utils-merge": { 1100 | "version": "1.0.1", 1101 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1102 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1103 | "engines": { 1104 | "node": ">= 0.4.0" 1105 | } 1106 | }, 1107 | "node_modules/vary": { 1108 | "version": "1.1.2", 1109 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1110 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1111 | "engines": { 1112 | "node": ">= 0.8" 1113 | } 1114 | }, 1115 | "node_modules/xtend": { 1116 | "version": "4.0.2", 1117 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 1118 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 1119 | "engines": { 1120 | "node": ">=0.4" 1121 | } 1122 | } 1123 | } 1124 | } 1125 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prefect-auth-proxy", 3 | "version": "1.0.0", 4 | "engines": { 5 | "node": ">=16" 6 | }, 7 | "description": "Prefect UI and CLI Authentication/Authorization Proxy for on-premises deployments", 8 | "main": "index.js", 9 | "scripts": { 10 | "start": "node index.js", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [ 14 | "prefect", 15 | "auth", 16 | "proxy", 17 | "nodejs" 18 | ], 19 | "author": "Murali@softrams.com", 20 | "license": "MIT", 21 | "dependencies": { 22 | "body-parser": "^1.20.2", 23 | "express": "^4.18.2", 24 | "http-proxy-middleware": "^2.0.6", 25 | "mysql": "^2.18.1", 26 | "node-cache": "^5.1.2", 27 | "pg": "^8.11.3", 28 | "pg-pool": "^3.6.1" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/softrams/prefect-auth-proxy" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/softrams/prefect-auth-proxy/issues" 36 | } 37 | } 38 | --------------------------------------------------------------------------------