├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── README.md ├── config └── authentication-policy-repository-config.json ├── cucumber-logo.png ├── images ├── change-password-diagram.JPG ├── codeProjectLogo.JPG ├── create-account-diagram.JPG ├── favicon.ico ├── forgot-password-diagram.JPG └── repos-design.JPG ├── package-lock.json ├── package.json ├── src ├── config │ └── password-policy-repository-impl.ts ├── core │ ├── authentication-flows-processor.ts │ └── authentication-user-impl.ts ├── crypto │ └── key-generator.ts ├── in-mem-impl │ └── authentication-account-inmem-repository.ts ├── index.ts ├── interceptors │ ├── create-account-interceptor.ts │ └── default-email-sender.ts ├── interfaces │ ├── authentication-user.ts │ ├── mail-sender.ts │ ├── password-policy-repository.ts │ └── repository │ │ └── authentication-account-repository.ts ├── types │ ├── account-enhancements.ts │ ├── authentication-flows-error.ts │ ├── authentication-policy.ts │ ├── flows-constatns.ts │ └── user-info.type.ts └── web │ └── user-action-controller.ts ├── test ├── crypto │ └── crpyto.test.ts └── mail │ └── mail.test.ts └── tsconfig.json /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [16.x, 18.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | # - run: npm test 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .idea 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # logo authentication-flows-js 2 | 3 | [![npm Package](https://img.shields.io/npm/v/authentication-flows-js.svg?style=flat-square)](https://www.npmjs.org/package/authentication-flows-js) 4 | 5 | ### authentication-flows-js is a powerful and highly customizable framework that covers all flows that any express-based authentication-server needs. 6 | 7 | [authentication-flows](https://github.com/OhadR/authentication-flows) for javascript 8 | 9 | 10 | ## Read the article in CodeProject: 11 | [![images/codeProjectLogo.JPG](images/codeProjectLogo.JPG)](https://www.codeproject.com/Articles/5300920/Authentication-flows-js) 12 | 13 | 14 | ## motivation 15 | 16 | Every secured web application should support these flows - unless it delegates the authentication to a third party 17 | (such as oAuth2.0). Thus, we end up in the same code written again and again. 18 | 19 | The `authentication-flows-js` module implements all authentication flows: 20 | 21 | * authentication 22 | * create account, 23 | * forgot password, 24 | * change password by user request, 25 | * force change password if password is expired, 26 | * locks the accont after pre-configured login failures. 27 | 28 | `authentication-flows-js` is a package that any express-based secured web applications can reuse, to get all the flows 29 | implemented, with a minimal set of configurations. 30 | This way developers can concentrate on developing the core of their app, instead of messing around with flows that are 31 | definitely not the core of their business. 32 | 33 | 34 | 35 | ## configuration 36 | 37 | ### sample application 38 | 39 | I have prepared a [sample application](https://github.com/OhadR/authentication-flows-js-app) that uses `authentication-flows-js` so it is a great place to start. Below there are 40 | the required configurations needed. 41 | 42 | ### repository adapters 43 | 44 | According to the design: 45 | 46 | ![images/repos-design.JPG](images/repos-design.JPG) 47 | 48 | 49 | The client-app chooses which repository it works with, and passes the appropriate adapters: 50 | 51 | const app = express(); 52 | var authFlows = require('authentication-flows-js'); 53 | const authFlowsES = require('authentication-flows-js-elasticsearch'); 54 | const esRepo = new authFlowsES.AuthenticationAccountElasticsearchRepository(); 55 | 56 | authFlows.config({ 57 | user_app: app, 58 | authenticationAccountRepository: repo, 59 | redirectAfterLogin, 60 | sendActivationEmailUponActivation [optional, see below] 61 | }); 62 | 63 | currently, the following repositories are supported: 64 | 65 | * [in-memory](https://github.com/OhadR/authentication-flows-js-inmem) 66 | * [elasticsearch](https://github.com/OhadR/authentication-flows-js-elasticsearch) 67 | * [gae-datasource](https://github.com/OhadR/authentication-flows-js-gae-datastore) 68 | 69 | ### express server object 70 | 71 | This module *reuses* that client-app' express server and adds several endpoints to it (e.g. `/createAccount`). 72 | Thus, the client-app should pass authentication-flows-js its server object (example above). 73 | 74 | ### password policy 75 | 76 | authentication-flows-js comes with a default set of configuration for the password policy (in 77 | `/config/authentication-policy-repository-config.json`). The hosting application can replace\edit the JSON file, and use 78 | its own preferred values. 79 | 80 | The password policy contains the following properties (with the following default values): 81 | 82 | passwordMinLength: 6, 83 | passwordMaxLength: 10, 84 | passwordMinUpCaseChars: 1, 85 | passwordMinLoCaseChars: 1, 86 | passwordMinNumbericDigits: 1, 87 | passwordMinSpecialSymbols: 1, 88 | passwordBlackList: ["password", "123456"], 89 | maxPasswordEntryAttempts: 5, 90 | passwordLifeInDays: 60 91 | 92 | an example for a client-app can be found [here](https://github.com/OhadR/authentication-flows-js-app). 93 | 94 | ## `body-parser` 95 | 96 | According to https://www.digitalocean.com/community/tutorials/use-expressjs-to-get-url-and-post-parameters, the client-app 97 | MUST use body-parser in order to be able to parse the body params. 98 | Thus, the `authentication-flows-js` can use: 99 | 100 | debug(`createAccount requestBody ${req.body}`); 101 | 102 | 103 | ## dependencies 104 | 105 | * `express` - this module uses web-api for flows such create-account, forget-password, etc. 106 | * `@log4js-node/log4js-api` - rather than being dependent on a specific version of`log4js` (to avoid headache for this library users). 107 | * `nodemailer` - sending verification emails. version 4.7.0 and NOT latest: https://stackoverflow.com/questions/54385031/nodemailer-fails-with-connection-refused-using-known-good-smtp-server/54537119#54537119 108 | 109 | 110 | ## required environment variables (for hosting-app and tests) 111 | 112 | DEBUG=*,-follow-redirects -express:* -body-parser:* 113 | emailSender 114 | smtpServer 115 | smtpPort 116 | emailServerUser 117 | emailServerPass 118 | 119 | ## run tests 120 | 121 | ts-node test\mail\mail.test.ts 122 | 123 | note: set the environment variables. 124 | 125 | ## deploy 126 | 127 | npm run build 128 | npm version patch 129 | npm publish 130 | 131 | ## emails 132 | 133 | This module sends verification emails. By default, it uses `nodemailer` and [SMTP2GO](https://www.smtp2go.com/), 134 | but it makes sense that each application has its own mailing system. In addition, verification emails 135 | may have the same look and feel of the hosting application. Hosing-application can have their own implementation by implementing `MailSender` interface. 136 | 137 | ## Flows 138 | 139 | ### Create Account 140 | 141 | ![create-account-diagram](images/create-account-diagram.JPG) 142 | 143 | ### Forgot Password 144 | 145 | ![forgot-password-diagram](images/forgot-password-diagram.JPG) 146 | 147 | ### Change Password 148 | 149 | ![change-password-diagram](images/change-password-diagram.JPG) 150 | 151 | ## API 152 | 153 | The AFM supports the below APIs: 154 | 155 | This URL renders the login page that is sent to the user: 156 | 157 | GET 158 | /login 159 | 160 | As mentioned earlier, the AFM manages also the authentication of the hosting application: 161 | 162 | POST 163 | /login 164 | username: string 165 | password: string 166 | 167 | By calling the URL, the hosting application can get the password policy. e.g. constraints like length, number of Capital 168 | letters required, number of digits required etc. This way the UI can alert the user if the password he chooses does not meet 169 | the requirements, before the request is sent to the server. 170 | 171 | GET 172 | /getPasswordConstraints 173 | 174 | renders the create account page that is sent to the user: 175 | 176 | GET 177 | /createAccount 178 | 179 | POST 180 | /createAccount 181 | 182 | GET 183 | /aa 184 | 185 | GET 186 | /forgotPassword 187 | 188 | POST 189 | /forgotPassword 190 | 191 | GET 192 | /rp 193 | 194 | POST 195 | /setNewPassword 196 | 197 | POST 198 | /deleteAccount 199 | 200 | GET 201 | /user get all users 202 | 203 | PUT 204 | /user/:email/authorities set authorities for user 205 | 206 | ## tests 207 | 208 | all flows are tested very clearly using [Cucumber automated tests](https://github.com/OhadR/authentication-flows-js-automation). 209 | 210 | 211 | 212 | 213 | ## refs 214 | 215 | https://softwareengineering.stackexchange.com/questions/424981/authentication-flows-for-secured-applications 216 | 217 | https://www.smashingmagazine.com/2020/03/creating-secure-password-flows-nodejs-mysql/ 218 | 219 | https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator 220 | 221 | ## Questions? issues? something is missing? 222 | 223 | Feel free to open issues here if you have any unclear matter or any other question. 224 | 225 | ## versions 226 | 227 | ### 1.12.2 228 | 229 | * bugfix 230 | * add in-mem impl from authentication-flows-js-inmem #23 231 | 232 | ### 1.12.0 233 | 234 | reveiled by automation: authentication failure - do not "redirect", but "render" instead, so can send 401 and message #22 235 | 236 | ### 1.11.0 237 | 238 | * store the last login date #21 239 | 240 | ### 1.10.0 241 | 242 | * add APIs for management - `getAllUsers`, and `setAuthorities`. so hosting app can manage authorization for users. #17 243 | 244 | ### 1.9.0 245 | 246 | * enable customized CreateAccountInterceptor #18 247 | 248 | ### 1.8.0 249 | 250 | * debug message: use prefix, so hosting app can omit the auth-flows logs using asterik (like ' -body-parser:*') #16 251 | * handle authorization: store authorities on the session (#17 related) 252 | 253 | ### 1.7.0 254 | 255 | * bugfix #17: rollback 1.4.0 'avoid sending activation email upon account-creation #11' 256 | 257 | ### 1.6.1 (1.6.0 is bad) 258 | 259 | * the subject of the email - allow it to contain the hosting-application's name #14 260 | 261 | ### 1.5.0 262 | 263 | * bugfix: after failed-authentication, redirect properly back to login page. currently there is "Unauthorized. Redirecting to /login" #12 264 | 265 | ### 1.4.0 266 | 267 | * avoid sending activation email upon account-creation #11 (see above) 268 | 269 | ### 1.3.0 270 | 271 | * redirect after login: till now, after login there was a redirect-back; but technically it led me back to login page, 272 | and this is not the desired behavior. So now I redirect to '/', and also the client app have the option to override this 273 | and to redirect to a different page, via the config option `redirectAfterLogin`. 274 | 275 | ### 1.2.0 276 | 277 | * bugfix | change password flow: get the token from the url correctly. 278 | 279 | ### 1.1.0 280 | 281 | * use GitHub-CI rather than Travis-CI. 282 | * re-arrange (dev-)dependencies in package.json 283 | * add build scripts (with rimraf). 284 | 285 | -------------------------------------------------------------------------------- /config/authentication-policy-repository-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "passwordMinLength": 6, 3 | "passwordMaxLength": 10, 4 | "passwordMinUpCaseChars": 1, 5 | "passwordMinLoCaseChars": 1, 6 | "passwordMinNumbericDigits": 1, 7 | "passwordMinSpecialSymbols": 1, 8 | "passwordBlackList": ["password", "123456"], 9 | "maxPasswordEntryAttempts": 5, 10 | "passwordLifeInDays": 60 11 | } -------------------------------------------------------------------------------- /cucumber-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OhadR/authentication-flows-js/619bb6738a9b0cd6c616f514b8b726fbcb417bfb/cucumber-logo.png -------------------------------------------------------------------------------- /images/change-password-diagram.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OhadR/authentication-flows-js/619bb6738a9b0cd6c616f514b8b726fbcb417bfb/images/change-password-diagram.JPG -------------------------------------------------------------------------------- /images/codeProjectLogo.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OhadR/authentication-flows-js/619bb6738a9b0cd6c616f514b8b726fbcb417bfb/images/codeProjectLogo.JPG -------------------------------------------------------------------------------- /images/create-account-diagram.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OhadR/authentication-flows-js/619bb6738a9b0cd6c616f514b8b726fbcb417bfb/images/create-account-diagram.JPG -------------------------------------------------------------------------------- /images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OhadR/authentication-flows-js/619bb6738a9b0cd6c616f514b8b726fbcb417bfb/images/favicon.ico -------------------------------------------------------------------------------- /images/forgot-password-diagram.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OhadR/authentication-flows-js/619bb6738a9b0cd6c616f514b8b726fbcb417bfb/images/forgot-password-diagram.JPG -------------------------------------------------------------------------------- /images/repos-design.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OhadR/authentication-flows-js/619bb6738a9b0cd6c616f514b8b726fbcb417bfb/images/repos-design.JPG -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authentication-flows-js", 3 | "version": "1.12.3", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "authentication-flows-js", 9 | "version": "1.12.3", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@log4js-node/log4js-api": "^1.0.2", 13 | "debug": "^4.3.1", 14 | "express": "^4.18.2", 15 | "nodemailer": "4.7.0", 16 | "proxy-agent": "^3.1.1" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^20.8.0", 20 | "rimraf": "^5.0.7", 21 | "ts-node": "^10.9.1", 22 | "typescript": "^5.2.2" 23 | } 24 | }, 25 | "node_modules/@cspotcode/source-map-support": { 26 | "version": "0.8.1", 27 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 28 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 29 | "dev": true, 30 | "dependencies": { 31 | "@jridgewell/trace-mapping": "0.3.9" 32 | }, 33 | "engines": { 34 | "node": ">=12" 35 | } 36 | }, 37 | "node_modules/@isaacs/cliui": { 38 | "version": "8.0.2", 39 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 40 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 41 | "dev": true, 42 | "dependencies": { 43 | "string-width": "^5.1.2", 44 | "string-width-cjs": "npm:string-width@^4.2.0", 45 | "strip-ansi": "^7.0.1", 46 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 47 | "wrap-ansi": "^8.1.0", 48 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 49 | }, 50 | "engines": { 51 | "node": ">=12" 52 | } 53 | }, 54 | "node_modules/@jridgewell/resolve-uri": { 55 | "version": "3.1.2", 56 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 57 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 58 | "dev": true, 59 | "engines": { 60 | "node": ">=6.0.0" 61 | } 62 | }, 63 | "node_modules/@jridgewell/sourcemap-codec": { 64 | "version": "1.4.15", 65 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 66 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 67 | "dev": true 68 | }, 69 | "node_modules/@jridgewell/trace-mapping": { 70 | "version": "0.3.9", 71 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 72 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 73 | "dev": true, 74 | "dependencies": { 75 | "@jridgewell/resolve-uri": "^3.0.3", 76 | "@jridgewell/sourcemap-codec": "^1.4.10" 77 | } 78 | }, 79 | "node_modules/@log4js-node/log4js-api": { 80 | "version": "1.0.2", 81 | "resolved": "https://registry.npmjs.org/@log4js-node/log4js-api/-/log4js-api-1.0.2.tgz", 82 | "integrity": "sha512-6SJfx949YEWooh/CUPpJ+F491y4BYJmknz4hUN1+RHvKoUEynKbRmhnwbk/VLmh4OthLLDNCyWXfbh4DG1cTXA==" 83 | }, 84 | "node_modules/@pkgjs/parseargs": { 85 | "version": "0.11.0", 86 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", 87 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", 88 | "dev": true, 89 | "optional": true, 90 | "engines": { 91 | "node": ">=14" 92 | } 93 | }, 94 | "node_modules/@tsconfig/node10": { 95 | "version": "1.0.11", 96 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", 97 | "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", 98 | "dev": true 99 | }, 100 | "node_modules/@tsconfig/node12": { 101 | "version": "1.0.11", 102 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 103 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 104 | "dev": true 105 | }, 106 | "node_modules/@tsconfig/node14": { 107 | "version": "1.0.3", 108 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 109 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 110 | "dev": true 111 | }, 112 | "node_modules/@tsconfig/node16": { 113 | "version": "1.0.4", 114 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 115 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", 116 | "dev": true 117 | }, 118 | "node_modules/@types/node": { 119 | "version": "20.12.13", 120 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.13.tgz", 121 | "integrity": "sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==", 122 | "dev": true, 123 | "dependencies": { 124 | "undici-types": "~5.26.4" 125 | } 126 | }, 127 | "node_modules/accepts": { 128 | "version": "1.3.8", 129 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 130 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 131 | "dependencies": { 132 | "mime-types": "~2.1.34", 133 | "negotiator": "0.6.3" 134 | }, 135 | "engines": { 136 | "node": ">= 0.6" 137 | } 138 | }, 139 | "node_modules/acorn": { 140 | "version": "8.11.3", 141 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 142 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 143 | "dev": true, 144 | "bin": { 145 | "acorn": "bin/acorn" 146 | }, 147 | "engines": { 148 | "node": ">=0.4.0" 149 | } 150 | }, 151 | "node_modules/acorn-walk": { 152 | "version": "8.3.2", 153 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", 154 | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", 155 | "dev": true, 156 | "engines": { 157 | "node": ">=0.4.0" 158 | } 159 | }, 160 | "node_modules/agent-base": { 161 | "version": "4.3.0", 162 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", 163 | "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", 164 | "dependencies": { 165 | "es6-promisify": "^5.0.0" 166 | }, 167 | "engines": { 168 | "node": ">= 4.0.0" 169 | } 170 | }, 171 | "node_modules/ansi-regex": { 172 | "version": "6.0.1", 173 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", 174 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", 175 | "dev": true, 176 | "engines": { 177 | "node": ">=12" 178 | }, 179 | "funding": { 180 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 181 | } 182 | }, 183 | "node_modules/ansi-styles": { 184 | "version": "6.2.1", 185 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 186 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 187 | "dev": true, 188 | "engines": { 189 | "node": ">=12" 190 | }, 191 | "funding": { 192 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 193 | } 194 | }, 195 | "node_modules/arg": { 196 | "version": "4.1.3", 197 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 198 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 199 | "dev": true 200 | }, 201 | "node_modules/array-flatten": { 202 | "version": "1.1.1", 203 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 204 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 205 | }, 206 | "node_modules/ast-types": { 207 | "version": "0.14.2", 208 | "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", 209 | "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", 210 | "dependencies": { 211 | "tslib": "^2.0.1" 212 | }, 213 | "engines": { 214 | "node": ">=4" 215 | } 216 | }, 217 | "node_modules/balanced-match": { 218 | "version": "1.0.2", 219 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 220 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 221 | "dev": true 222 | }, 223 | "node_modules/body-parser": { 224 | "version": "1.20.2", 225 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", 226 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", 227 | "dependencies": { 228 | "bytes": "3.1.2", 229 | "content-type": "~1.0.5", 230 | "debug": "2.6.9", 231 | "depd": "2.0.0", 232 | "destroy": "1.2.0", 233 | "http-errors": "2.0.0", 234 | "iconv-lite": "0.4.24", 235 | "on-finished": "2.4.1", 236 | "qs": "6.11.0", 237 | "raw-body": "2.5.2", 238 | "type-is": "~1.6.18", 239 | "unpipe": "1.0.0" 240 | }, 241 | "engines": { 242 | "node": ">= 0.8", 243 | "npm": "1.2.8000 || >= 1.4.16" 244 | } 245 | }, 246 | "node_modules/body-parser/node_modules/debug": { 247 | "version": "2.6.9", 248 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 249 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 250 | "dependencies": { 251 | "ms": "2.0.0" 252 | } 253 | }, 254 | "node_modules/body-parser/node_modules/ms": { 255 | "version": "2.0.0", 256 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 257 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 258 | }, 259 | "node_modules/brace-expansion": { 260 | "version": "2.0.1", 261 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 262 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 263 | "dev": true, 264 | "dependencies": { 265 | "balanced-match": "^1.0.0" 266 | } 267 | }, 268 | "node_modules/bytes": { 269 | "version": "3.1.2", 270 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 271 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 272 | "engines": { 273 | "node": ">= 0.8" 274 | } 275 | }, 276 | "node_modules/call-bind": { 277 | "version": "1.0.7", 278 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 279 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 280 | "dependencies": { 281 | "es-define-property": "^1.0.0", 282 | "es-errors": "^1.3.0", 283 | "function-bind": "^1.1.2", 284 | "get-intrinsic": "^1.2.4", 285 | "set-function-length": "^1.2.1" 286 | }, 287 | "engines": { 288 | "node": ">= 0.4" 289 | }, 290 | "funding": { 291 | "url": "https://github.com/sponsors/ljharb" 292 | } 293 | }, 294 | "node_modules/co": { 295 | "version": "4.6.0", 296 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 297 | "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", 298 | "engines": { 299 | "iojs": ">= 1.0.0", 300 | "node": ">= 0.12.0" 301 | } 302 | }, 303 | "node_modules/color-convert": { 304 | "version": "2.0.1", 305 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 306 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 307 | "dev": true, 308 | "dependencies": { 309 | "color-name": "~1.1.4" 310 | }, 311 | "engines": { 312 | "node": ">=7.0.0" 313 | } 314 | }, 315 | "node_modules/color-name": { 316 | "version": "1.1.4", 317 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 318 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 319 | "dev": true 320 | }, 321 | "node_modules/content-disposition": { 322 | "version": "0.5.4", 323 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 324 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 325 | "dependencies": { 326 | "safe-buffer": "5.2.1" 327 | }, 328 | "engines": { 329 | "node": ">= 0.6" 330 | } 331 | }, 332 | "node_modules/content-type": { 333 | "version": "1.0.5", 334 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 335 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 336 | "engines": { 337 | "node": ">= 0.6" 338 | } 339 | }, 340 | "node_modules/cookie": { 341 | "version": "0.6.0", 342 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", 343 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", 344 | "engines": { 345 | "node": ">= 0.6" 346 | } 347 | }, 348 | "node_modules/cookie-signature": { 349 | "version": "1.0.6", 350 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 351 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 352 | }, 353 | "node_modules/core-util-is": { 354 | "version": "1.0.3", 355 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 356 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 357 | }, 358 | "node_modules/create-require": { 359 | "version": "1.1.1", 360 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 361 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 362 | "dev": true 363 | }, 364 | "node_modules/cross-spawn": { 365 | "version": "7.0.3", 366 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 367 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 368 | "dev": true, 369 | "dependencies": { 370 | "path-key": "^3.1.0", 371 | "shebang-command": "^2.0.0", 372 | "which": "^2.0.1" 373 | }, 374 | "engines": { 375 | "node": ">= 8" 376 | } 377 | }, 378 | "node_modules/data-uri-to-buffer": { 379 | "version": "1.2.0", 380 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", 381 | "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==" 382 | }, 383 | "node_modules/debug": { 384 | "version": "4.3.5", 385 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", 386 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", 387 | "dependencies": { 388 | "ms": "2.1.2" 389 | }, 390 | "engines": { 391 | "node": ">=6.0" 392 | }, 393 | "peerDependenciesMeta": { 394 | "supports-color": { 395 | "optional": true 396 | } 397 | } 398 | }, 399 | "node_modules/deep-is": { 400 | "version": "0.1.4", 401 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 402 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" 403 | }, 404 | "node_modules/define-data-property": { 405 | "version": "1.1.4", 406 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 407 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 408 | "dependencies": { 409 | "es-define-property": "^1.0.0", 410 | "es-errors": "^1.3.0", 411 | "gopd": "^1.0.1" 412 | }, 413 | "engines": { 414 | "node": ">= 0.4" 415 | }, 416 | "funding": { 417 | "url": "https://github.com/sponsors/ljharb" 418 | } 419 | }, 420 | "node_modules/degenerator": { 421 | "version": "1.0.4", 422 | "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", 423 | "integrity": "sha512-EMAC+riLSC64jKfOs1jp8J7M4ZXstUUwTdwFBEv6HOzL/Ae+eAzMKEK0nJnpof2fnw9IOjmE6u6qXFejVyk8AA==", 424 | "dependencies": { 425 | "ast-types": "0.x.x", 426 | "escodegen": "1.x.x", 427 | "esprima": "3.x.x" 428 | } 429 | }, 430 | "node_modules/depd": { 431 | "version": "2.0.0", 432 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 433 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 434 | "engines": { 435 | "node": ">= 0.8" 436 | } 437 | }, 438 | "node_modules/destroy": { 439 | "version": "1.2.0", 440 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 441 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 442 | "engines": { 443 | "node": ">= 0.8", 444 | "npm": "1.2.8000 || >= 1.4.16" 445 | } 446 | }, 447 | "node_modules/diff": { 448 | "version": "4.0.2", 449 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 450 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 451 | "dev": true, 452 | "engines": { 453 | "node": ">=0.3.1" 454 | } 455 | }, 456 | "node_modules/eastasianwidth": { 457 | "version": "0.2.0", 458 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 459 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 460 | "dev": true 461 | }, 462 | "node_modules/ee-first": { 463 | "version": "1.1.1", 464 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 465 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 466 | }, 467 | "node_modules/emoji-regex": { 468 | "version": "9.2.2", 469 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 470 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 471 | "dev": true 472 | }, 473 | "node_modules/encodeurl": { 474 | "version": "1.0.2", 475 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 476 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 477 | "engines": { 478 | "node": ">= 0.8" 479 | } 480 | }, 481 | "node_modules/es-define-property": { 482 | "version": "1.0.0", 483 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 484 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 485 | "dependencies": { 486 | "get-intrinsic": "^1.2.4" 487 | }, 488 | "engines": { 489 | "node": ">= 0.4" 490 | } 491 | }, 492 | "node_modules/es-errors": { 493 | "version": "1.3.0", 494 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 495 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 496 | "engines": { 497 | "node": ">= 0.4" 498 | } 499 | }, 500 | "node_modules/es6-promise": { 501 | "version": "4.2.8", 502 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 503 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" 504 | }, 505 | "node_modules/es6-promisify": { 506 | "version": "5.0.0", 507 | "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 508 | "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", 509 | "dependencies": { 510 | "es6-promise": "^4.0.3" 511 | } 512 | }, 513 | "node_modules/escape-html": { 514 | "version": "1.0.3", 515 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 516 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 517 | }, 518 | "node_modules/escodegen": { 519 | "version": "1.14.3", 520 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", 521 | "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", 522 | "dependencies": { 523 | "esprima": "^4.0.1", 524 | "estraverse": "^4.2.0", 525 | "esutils": "^2.0.2", 526 | "optionator": "^0.8.1" 527 | }, 528 | "bin": { 529 | "escodegen": "bin/escodegen.js", 530 | "esgenerate": "bin/esgenerate.js" 531 | }, 532 | "engines": { 533 | "node": ">=4.0" 534 | }, 535 | "optionalDependencies": { 536 | "source-map": "~0.6.1" 537 | } 538 | }, 539 | "node_modules/escodegen/node_modules/esprima": { 540 | "version": "4.0.1", 541 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 542 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 543 | "bin": { 544 | "esparse": "bin/esparse.js", 545 | "esvalidate": "bin/esvalidate.js" 546 | }, 547 | "engines": { 548 | "node": ">=4" 549 | } 550 | }, 551 | "node_modules/esprima": { 552 | "version": "3.1.3", 553 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", 554 | "integrity": "sha512-AWwVMNxwhN8+NIPQzAQZCm7RkLC4RbM3B1OobMuyp3i+w73X57KCKaVIxaRZb+DYCojq7rspo+fmuQfAboyhFg==", 555 | "bin": { 556 | "esparse": "bin/esparse.js", 557 | "esvalidate": "bin/esvalidate.js" 558 | }, 559 | "engines": { 560 | "node": ">=4" 561 | } 562 | }, 563 | "node_modules/estraverse": { 564 | "version": "4.3.0", 565 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 566 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", 567 | "engines": { 568 | "node": ">=4.0" 569 | } 570 | }, 571 | "node_modules/esutils": { 572 | "version": "2.0.3", 573 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 574 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 575 | "engines": { 576 | "node": ">=0.10.0" 577 | } 578 | }, 579 | "node_modules/etag": { 580 | "version": "1.8.1", 581 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 582 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 583 | "engines": { 584 | "node": ">= 0.6" 585 | } 586 | }, 587 | "node_modules/express": { 588 | "version": "4.19.2", 589 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", 590 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", 591 | "dependencies": { 592 | "accepts": "~1.3.8", 593 | "array-flatten": "1.1.1", 594 | "body-parser": "1.20.2", 595 | "content-disposition": "0.5.4", 596 | "content-type": "~1.0.4", 597 | "cookie": "0.6.0", 598 | "cookie-signature": "1.0.6", 599 | "debug": "2.6.9", 600 | "depd": "2.0.0", 601 | "encodeurl": "~1.0.2", 602 | "escape-html": "~1.0.3", 603 | "etag": "~1.8.1", 604 | "finalhandler": "1.2.0", 605 | "fresh": "0.5.2", 606 | "http-errors": "2.0.0", 607 | "merge-descriptors": "1.0.1", 608 | "methods": "~1.1.2", 609 | "on-finished": "2.4.1", 610 | "parseurl": "~1.3.3", 611 | "path-to-regexp": "0.1.7", 612 | "proxy-addr": "~2.0.7", 613 | "qs": "6.11.0", 614 | "range-parser": "~1.2.1", 615 | "safe-buffer": "5.2.1", 616 | "send": "0.18.0", 617 | "serve-static": "1.15.0", 618 | "setprototypeof": "1.2.0", 619 | "statuses": "2.0.1", 620 | "type-is": "~1.6.18", 621 | "utils-merge": "1.0.1", 622 | "vary": "~1.1.2" 623 | }, 624 | "engines": { 625 | "node": ">= 0.10.0" 626 | } 627 | }, 628 | "node_modules/express/node_modules/debug": { 629 | "version": "2.6.9", 630 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 631 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 632 | "dependencies": { 633 | "ms": "2.0.0" 634 | } 635 | }, 636 | "node_modules/express/node_modules/ms": { 637 | "version": "2.0.0", 638 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 639 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 640 | }, 641 | "node_modules/extend": { 642 | "version": "3.0.2", 643 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 644 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 645 | }, 646 | "node_modules/fast-levenshtein": { 647 | "version": "2.0.6", 648 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 649 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" 650 | }, 651 | "node_modules/file-uri-to-path": { 652 | "version": "1.0.0", 653 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 654 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" 655 | }, 656 | "node_modules/finalhandler": { 657 | "version": "1.2.0", 658 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 659 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 660 | "dependencies": { 661 | "debug": "2.6.9", 662 | "encodeurl": "~1.0.2", 663 | "escape-html": "~1.0.3", 664 | "on-finished": "2.4.1", 665 | "parseurl": "~1.3.3", 666 | "statuses": "2.0.1", 667 | "unpipe": "~1.0.0" 668 | }, 669 | "engines": { 670 | "node": ">= 0.8" 671 | } 672 | }, 673 | "node_modules/finalhandler/node_modules/debug": { 674 | "version": "2.6.9", 675 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 676 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 677 | "dependencies": { 678 | "ms": "2.0.0" 679 | } 680 | }, 681 | "node_modules/finalhandler/node_modules/ms": { 682 | "version": "2.0.0", 683 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 684 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 685 | }, 686 | "node_modules/foreground-child": { 687 | "version": "3.1.1", 688 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", 689 | "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", 690 | "dev": true, 691 | "dependencies": { 692 | "cross-spawn": "^7.0.0", 693 | "signal-exit": "^4.0.1" 694 | }, 695 | "engines": { 696 | "node": ">=14" 697 | }, 698 | "funding": { 699 | "url": "https://github.com/sponsors/isaacs" 700 | } 701 | }, 702 | "node_modules/forwarded": { 703 | "version": "0.2.0", 704 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 705 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 706 | "engines": { 707 | "node": ">= 0.6" 708 | } 709 | }, 710 | "node_modules/fresh": { 711 | "version": "0.5.2", 712 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 713 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 714 | "engines": { 715 | "node": ">= 0.6" 716 | } 717 | }, 718 | "node_modules/ftp": { 719 | "version": "0.3.10", 720 | "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", 721 | "integrity": "sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==", 722 | "dependencies": { 723 | "readable-stream": "1.1.x", 724 | "xregexp": "2.0.0" 725 | }, 726 | "engines": { 727 | "node": ">=0.8.0" 728 | } 729 | }, 730 | "node_modules/ftp/node_modules/isarray": { 731 | "version": "0.0.1", 732 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 733 | "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" 734 | }, 735 | "node_modules/ftp/node_modules/readable-stream": { 736 | "version": "1.1.14", 737 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 738 | "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", 739 | "dependencies": { 740 | "core-util-is": "~1.0.0", 741 | "inherits": "~2.0.1", 742 | "isarray": "0.0.1", 743 | "string_decoder": "~0.10.x" 744 | } 745 | }, 746 | "node_modules/ftp/node_modules/string_decoder": { 747 | "version": "0.10.31", 748 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 749 | "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" 750 | }, 751 | "node_modules/function-bind": { 752 | "version": "1.1.2", 753 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 754 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 755 | "funding": { 756 | "url": "https://github.com/sponsors/ljharb" 757 | } 758 | }, 759 | "node_modules/get-intrinsic": { 760 | "version": "1.2.4", 761 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 762 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 763 | "dependencies": { 764 | "es-errors": "^1.3.0", 765 | "function-bind": "^1.1.2", 766 | "has-proto": "^1.0.1", 767 | "has-symbols": "^1.0.3", 768 | "hasown": "^2.0.0" 769 | }, 770 | "engines": { 771 | "node": ">= 0.4" 772 | }, 773 | "funding": { 774 | "url": "https://github.com/sponsors/ljharb" 775 | } 776 | }, 777 | "node_modules/get-uri": { 778 | "version": "2.0.4", 779 | "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz", 780 | "integrity": "sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==", 781 | "dependencies": { 782 | "data-uri-to-buffer": "1", 783 | "debug": "2", 784 | "extend": "~3.0.2", 785 | "file-uri-to-path": "1", 786 | "ftp": "~0.3.10", 787 | "readable-stream": "2" 788 | } 789 | }, 790 | "node_modules/get-uri/node_modules/debug": { 791 | "version": "2.6.9", 792 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 793 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 794 | "dependencies": { 795 | "ms": "2.0.0" 796 | } 797 | }, 798 | "node_modules/get-uri/node_modules/ms": { 799 | "version": "2.0.0", 800 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 801 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 802 | }, 803 | "node_modules/glob": { 804 | "version": "10.4.1", 805 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", 806 | "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", 807 | "dev": true, 808 | "dependencies": { 809 | "foreground-child": "^3.1.0", 810 | "jackspeak": "^3.1.2", 811 | "minimatch": "^9.0.4", 812 | "minipass": "^7.1.2", 813 | "path-scurry": "^1.11.1" 814 | }, 815 | "bin": { 816 | "glob": "dist/esm/bin.mjs" 817 | }, 818 | "engines": { 819 | "node": ">=16 || 14 >=14.18" 820 | }, 821 | "funding": { 822 | "url": "https://github.com/sponsors/isaacs" 823 | } 824 | }, 825 | "node_modules/gopd": { 826 | "version": "1.0.1", 827 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 828 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 829 | "dependencies": { 830 | "get-intrinsic": "^1.1.3" 831 | }, 832 | "funding": { 833 | "url": "https://github.com/sponsors/ljharb" 834 | } 835 | }, 836 | "node_modules/has-property-descriptors": { 837 | "version": "1.0.2", 838 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 839 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 840 | "dependencies": { 841 | "es-define-property": "^1.0.0" 842 | }, 843 | "funding": { 844 | "url": "https://github.com/sponsors/ljharb" 845 | } 846 | }, 847 | "node_modules/has-proto": { 848 | "version": "1.0.3", 849 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 850 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 851 | "engines": { 852 | "node": ">= 0.4" 853 | }, 854 | "funding": { 855 | "url": "https://github.com/sponsors/ljharb" 856 | } 857 | }, 858 | "node_modules/has-symbols": { 859 | "version": "1.0.3", 860 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 861 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 862 | "engines": { 863 | "node": ">= 0.4" 864 | }, 865 | "funding": { 866 | "url": "https://github.com/sponsors/ljharb" 867 | } 868 | }, 869 | "node_modules/hasown": { 870 | "version": "2.0.2", 871 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 872 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 873 | "dependencies": { 874 | "function-bind": "^1.1.2" 875 | }, 876 | "engines": { 877 | "node": ">= 0.4" 878 | } 879 | }, 880 | "node_modules/http-errors": { 881 | "version": "2.0.0", 882 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 883 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 884 | "dependencies": { 885 | "depd": "2.0.0", 886 | "inherits": "2.0.4", 887 | "setprototypeof": "1.2.0", 888 | "statuses": "2.0.1", 889 | "toidentifier": "1.0.1" 890 | }, 891 | "engines": { 892 | "node": ">= 0.8" 893 | } 894 | }, 895 | "node_modules/http-proxy-agent": { 896 | "version": "2.1.0", 897 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", 898 | "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", 899 | "dependencies": { 900 | "agent-base": "4", 901 | "debug": "3.1.0" 902 | }, 903 | "engines": { 904 | "node": ">= 4.5.0" 905 | } 906 | }, 907 | "node_modules/http-proxy-agent/node_modules/debug": { 908 | "version": "3.1.0", 909 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 910 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 911 | "dependencies": { 912 | "ms": "2.0.0" 913 | } 914 | }, 915 | "node_modules/http-proxy-agent/node_modules/ms": { 916 | "version": "2.0.0", 917 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 918 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 919 | }, 920 | "node_modules/https-proxy-agent": { 921 | "version": "3.0.1", 922 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", 923 | "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", 924 | "dependencies": { 925 | "agent-base": "^4.3.0", 926 | "debug": "^3.1.0" 927 | }, 928 | "engines": { 929 | "node": ">= 4.5.0" 930 | } 931 | }, 932 | "node_modules/https-proxy-agent/node_modules/debug": { 933 | "version": "3.2.7", 934 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 935 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 936 | "dependencies": { 937 | "ms": "^2.1.1" 938 | } 939 | }, 940 | "node_modules/iconv-lite": { 941 | "version": "0.4.24", 942 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 943 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 944 | "dependencies": { 945 | "safer-buffer": ">= 2.1.2 < 3" 946 | }, 947 | "engines": { 948 | "node": ">=0.10.0" 949 | } 950 | }, 951 | "node_modules/inherits": { 952 | "version": "2.0.4", 953 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 954 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 955 | }, 956 | "node_modules/ip": { 957 | "version": "1.1.9", 958 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", 959 | "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" 960 | }, 961 | "node_modules/ipaddr.js": { 962 | "version": "1.9.1", 963 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 964 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 965 | "engines": { 966 | "node": ">= 0.10" 967 | } 968 | }, 969 | "node_modules/is-fullwidth-code-point": { 970 | "version": "3.0.0", 971 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 972 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 973 | "dev": true, 974 | "engines": { 975 | "node": ">=8" 976 | } 977 | }, 978 | "node_modules/isarray": { 979 | "version": "1.0.0", 980 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 981 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 982 | }, 983 | "node_modules/isexe": { 984 | "version": "2.0.0", 985 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 986 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 987 | "dev": true 988 | }, 989 | "node_modules/jackspeak": { 990 | "version": "3.1.2", 991 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", 992 | "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", 993 | "dev": true, 994 | "dependencies": { 995 | "@isaacs/cliui": "^8.0.2" 996 | }, 997 | "engines": { 998 | "node": ">=14" 999 | }, 1000 | "funding": { 1001 | "url": "https://github.com/sponsors/isaacs" 1002 | }, 1003 | "optionalDependencies": { 1004 | "@pkgjs/parseargs": "^0.11.0" 1005 | } 1006 | }, 1007 | "node_modules/levn": { 1008 | "version": "0.3.0", 1009 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 1010 | "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", 1011 | "dependencies": { 1012 | "prelude-ls": "~1.1.2", 1013 | "type-check": "~0.3.2" 1014 | }, 1015 | "engines": { 1016 | "node": ">= 0.8.0" 1017 | } 1018 | }, 1019 | "node_modules/lru-cache": { 1020 | "version": "5.1.1", 1021 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 1022 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 1023 | "dependencies": { 1024 | "yallist": "^3.0.2" 1025 | } 1026 | }, 1027 | "node_modules/make-error": { 1028 | "version": "1.3.6", 1029 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 1030 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 1031 | "dev": true 1032 | }, 1033 | "node_modules/media-typer": { 1034 | "version": "0.3.0", 1035 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1036 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 1037 | "engines": { 1038 | "node": ">= 0.6" 1039 | } 1040 | }, 1041 | "node_modules/merge-descriptors": { 1042 | "version": "1.0.1", 1043 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1044 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 1045 | }, 1046 | "node_modules/methods": { 1047 | "version": "1.1.2", 1048 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1049 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 1050 | "engines": { 1051 | "node": ">= 0.6" 1052 | } 1053 | }, 1054 | "node_modules/mime": { 1055 | "version": "1.6.0", 1056 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1057 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1058 | "bin": { 1059 | "mime": "cli.js" 1060 | }, 1061 | "engines": { 1062 | "node": ">=4" 1063 | } 1064 | }, 1065 | "node_modules/mime-db": { 1066 | "version": "1.52.0", 1067 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1068 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1069 | "engines": { 1070 | "node": ">= 0.6" 1071 | } 1072 | }, 1073 | "node_modules/mime-types": { 1074 | "version": "2.1.35", 1075 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1076 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1077 | "dependencies": { 1078 | "mime-db": "1.52.0" 1079 | }, 1080 | "engines": { 1081 | "node": ">= 0.6" 1082 | } 1083 | }, 1084 | "node_modules/minimatch": { 1085 | "version": "9.0.4", 1086 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", 1087 | "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", 1088 | "dev": true, 1089 | "dependencies": { 1090 | "brace-expansion": "^2.0.1" 1091 | }, 1092 | "engines": { 1093 | "node": ">=16 || 14 >=14.17" 1094 | }, 1095 | "funding": { 1096 | "url": "https://github.com/sponsors/isaacs" 1097 | } 1098 | }, 1099 | "node_modules/minipass": { 1100 | "version": "7.1.2", 1101 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", 1102 | "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", 1103 | "dev": true, 1104 | "engines": { 1105 | "node": ">=16 || 14 >=14.17" 1106 | } 1107 | }, 1108 | "node_modules/ms": { 1109 | "version": "2.1.2", 1110 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1111 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1112 | }, 1113 | "node_modules/negotiator": { 1114 | "version": "0.6.3", 1115 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1116 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1117 | "engines": { 1118 | "node": ">= 0.6" 1119 | } 1120 | }, 1121 | "node_modules/netmask": { 1122 | "version": "1.0.6", 1123 | "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", 1124 | "integrity": "sha512-3DWDqAtIiPSkBXZyYEjwebfK56nrlQfRGt642fu8RPaL+ePu750+HCMHxjJCG3iEHq/0aeMvX6KIzlv7nuhfrA==", 1125 | "engines": { 1126 | "node": ">= 0.4.0" 1127 | } 1128 | }, 1129 | "node_modules/nodemailer": { 1130 | "version": "4.7.0", 1131 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.7.0.tgz", 1132 | "integrity": "sha512-IludxDypFpYw4xpzKdMAozBSkzKHmNBvGanUREjJItgJ2NYcK/s8+PggVhj7c2yGFQykKsnnmv1+Aqo0ZfjHmw==", 1133 | "engines": { 1134 | "node": ">=6.0.0" 1135 | } 1136 | }, 1137 | "node_modules/object-inspect": { 1138 | "version": "1.13.1", 1139 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 1140 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", 1141 | "funding": { 1142 | "url": "https://github.com/sponsors/ljharb" 1143 | } 1144 | }, 1145 | "node_modules/on-finished": { 1146 | "version": "2.4.1", 1147 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1148 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1149 | "dependencies": { 1150 | "ee-first": "1.1.1" 1151 | }, 1152 | "engines": { 1153 | "node": ">= 0.8" 1154 | } 1155 | }, 1156 | "node_modules/optionator": { 1157 | "version": "0.8.3", 1158 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", 1159 | "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", 1160 | "dependencies": { 1161 | "deep-is": "~0.1.3", 1162 | "fast-levenshtein": "~2.0.6", 1163 | "levn": "~0.3.0", 1164 | "prelude-ls": "~1.1.2", 1165 | "type-check": "~0.3.2", 1166 | "word-wrap": "~1.2.3" 1167 | }, 1168 | "engines": { 1169 | "node": ">= 0.8.0" 1170 | } 1171 | }, 1172 | "node_modules/pac-proxy-agent": { 1173 | "version": "3.0.1", 1174 | "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz", 1175 | "integrity": "sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ==", 1176 | "dependencies": { 1177 | "agent-base": "^4.2.0", 1178 | "debug": "^4.1.1", 1179 | "get-uri": "^2.0.0", 1180 | "http-proxy-agent": "^2.1.0", 1181 | "https-proxy-agent": "^3.0.0", 1182 | "pac-resolver": "^3.0.0", 1183 | "raw-body": "^2.2.0", 1184 | "socks-proxy-agent": "^4.0.1" 1185 | } 1186 | }, 1187 | "node_modules/pac-resolver": { 1188 | "version": "3.0.0", 1189 | "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", 1190 | "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", 1191 | "dependencies": { 1192 | "co": "^4.6.0", 1193 | "degenerator": "^1.0.4", 1194 | "ip": "^1.1.5", 1195 | "netmask": "^1.0.6", 1196 | "thunkify": "^2.1.2" 1197 | } 1198 | }, 1199 | "node_modules/parseurl": { 1200 | "version": "1.3.3", 1201 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1202 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1203 | "engines": { 1204 | "node": ">= 0.8" 1205 | } 1206 | }, 1207 | "node_modules/path-key": { 1208 | "version": "3.1.1", 1209 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1210 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1211 | "dev": true, 1212 | "engines": { 1213 | "node": ">=8" 1214 | } 1215 | }, 1216 | "node_modules/path-scurry": { 1217 | "version": "1.11.1", 1218 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", 1219 | "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", 1220 | "dev": true, 1221 | "dependencies": { 1222 | "lru-cache": "^10.2.0", 1223 | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" 1224 | }, 1225 | "engines": { 1226 | "node": ">=16 || 14 >=14.18" 1227 | }, 1228 | "funding": { 1229 | "url": "https://github.com/sponsors/isaacs" 1230 | } 1231 | }, 1232 | "node_modules/path-scurry/node_modules/lru-cache": { 1233 | "version": "10.2.2", 1234 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", 1235 | "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", 1236 | "dev": true, 1237 | "engines": { 1238 | "node": "14 || >=16.14" 1239 | } 1240 | }, 1241 | "node_modules/path-to-regexp": { 1242 | "version": "0.1.7", 1243 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1244 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 1245 | }, 1246 | "node_modules/prelude-ls": { 1247 | "version": "1.1.2", 1248 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 1249 | "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", 1250 | "engines": { 1251 | "node": ">= 0.8.0" 1252 | } 1253 | }, 1254 | "node_modules/process-nextick-args": { 1255 | "version": "2.0.1", 1256 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1257 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 1258 | }, 1259 | "node_modules/proxy-addr": { 1260 | "version": "2.0.7", 1261 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1262 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1263 | "dependencies": { 1264 | "forwarded": "0.2.0", 1265 | "ipaddr.js": "1.9.1" 1266 | }, 1267 | "engines": { 1268 | "node": ">= 0.10" 1269 | } 1270 | }, 1271 | "node_modules/proxy-agent": { 1272 | "version": "3.1.1", 1273 | "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.1.1.tgz", 1274 | "integrity": "sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw==", 1275 | "dependencies": { 1276 | "agent-base": "^4.2.0", 1277 | "debug": "4", 1278 | "http-proxy-agent": "^2.1.0", 1279 | "https-proxy-agent": "^3.0.0", 1280 | "lru-cache": "^5.1.1", 1281 | "pac-proxy-agent": "^3.0.1", 1282 | "proxy-from-env": "^1.0.0", 1283 | "socks-proxy-agent": "^4.0.1" 1284 | }, 1285 | "engines": { 1286 | "node": ">=6" 1287 | } 1288 | }, 1289 | "node_modules/proxy-from-env": { 1290 | "version": "1.1.0", 1291 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 1292 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 1293 | }, 1294 | "node_modules/qs": { 1295 | "version": "6.11.0", 1296 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 1297 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 1298 | "dependencies": { 1299 | "side-channel": "^1.0.4" 1300 | }, 1301 | "engines": { 1302 | "node": ">=0.6" 1303 | }, 1304 | "funding": { 1305 | "url": "https://github.com/sponsors/ljharb" 1306 | } 1307 | }, 1308 | "node_modules/range-parser": { 1309 | "version": "1.2.1", 1310 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1311 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1312 | "engines": { 1313 | "node": ">= 0.6" 1314 | } 1315 | }, 1316 | "node_modules/raw-body": { 1317 | "version": "2.5.2", 1318 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 1319 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 1320 | "dependencies": { 1321 | "bytes": "3.1.2", 1322 | "http-errors": "2.0.0", 1323 | "iconv-lite": "0.4.24", 1324 | "unpipe": "1.0.0" 1325 | }, 1326 | "engines": { 1327 | "node": ">= 0.8" 1328 | } 1329 | }, 1330 | "node_modules/readable-stream": { 1331 | "version": "2.3.8", 1332 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 1333 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 1334 | "dependencies": { 1335 | "core-util-is": "~1.0.0", 1336 | "inherits": "~2.0.3", 1337 | "isarray": "~1.0.0", 1338 | "process-nextick-args": "~2.0.0", 1339 | "safe-buffer": "~5.1.1", 1340 | "string_decoder": "~1.1.1", 1341 | "util-deprecate": "~1.0.1" 1342 | } 1343 | }, 1344 | "node_modules/readable-stream/node_modules/safe-buffer": { 1345 | "version": "5.1.2", 1346 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1347 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1348 | }, 1349 | "node_modules/rimraf": { 1350 | "version": "5.0.7", 1351 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", 1352 | "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", 1353 | "dev": true, 1354 | "dependencies": { 1355 | "glob": "^10.3.7" 1356 | }, 1357 | "bin": { 1358 | "rimraf": "dist/esm/bin.mjs" 1359 | }, 1360 | "engines": { 1361 | "node": ">=14.18" 1362 | }, 1363 | "funding": { 1364 | "url": "https://github.com/sponsors/isaacs" 1365 | } 1366 | }, 1367 | "node_modules/safe-buffer": { 1368 | "version": "5.2.1", 1369 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1370 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1371 | "funding": [ 1372 | { 1373 | "type": "github", 1374 | "url": "https://github.com/sponsors/feross" 1375 | }, 1376 | { 1377 | "type": "patreon", 1378 | "url": "https://www.patreon.com/feross" 1379 | }, 1380 | { 1381 | "type": "consulting", 1382 | "url": "https://feross.org/support" 1383 | } 1384 | ] 1385 | }, 1386 | "node_modules/safer-buffer": { 1387 | "version": "2.1.2", 1388 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1389 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1390 | }, 1391 | "node_modules/send": { 1392 | "version": "0.18.0", 1393 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 1394 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 1395 | "dependencies": { 1396 | "debug": "2.6.9", 1397 | "depd": "2.0.0", 1398 | "destroy": "1.2.0", 1399 | "encodeurl": "~1.0.2", 1400 | "escape-html": "~1.0.3", 1401 | "etag": "~1.8.1", 1402 | "fresh": "0.5.2", 1403 | "http-errors": "2.0.0", 1404 | "mime": "1.6.0", 1405 | "ms": "2.1.3", 1406 | "on-finished": "2.4.1", 1407 | "range-parser": "~1.2.1", 1408 | "statuses": "2.0.1" 1409 | }, 1410 | "engines": { 1411 | "node": ">= 0.8.0" 1412 | } 1413 | }, 1414 | "node_modules/send/node_modules/debug": { 1415 | "version": "2.6.9", 1416 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1417 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1418 | "dependencies": { 1419 | "ms": "2.0.0" 1420 | } 1421 | }, 1422 | "node_modules/send/node_modules/debug/node_modules/ms": { 1423 | "version": "2.0.0", 1424 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1425 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1426 | }, 1427 | "node_modules/send/node_modules/ms": { 1428 | "version": "2.1.3", 1429 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1430 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1431 | }, 1432 | "node_modules/serve-static": { 1433 | "version": "1.15.0", 1434 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 1435 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 1436 | "dependencies": { 1437 | "encodeurl": "~1.0.2", 1438 | "escape-html": "~1.0.3", 1439 | "parseurl": "~1.3.3", 1440 | "send": "0.18.0" 1441 | }, 1442 | "engines": { 1443 | "node": ">= 0.8.0" 1444 | } 1445 | }, 1446 | "node_modules/set-function-length": { 1447 | "version": "1.2.2", 1448 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 1449 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 1450 | "dependencies": { 1451 | "define-data-property": "^1.1.4", 1452 | "es-errors": "^1.3.0", 1453 | "function-bind": "^1.1.2", 1454 | "get-intrinsic": "^1.2.4", 1455 | "gopd": "^1.0.1", 1456 | "has-property-descriptors": "^1.0.2" 1457 | }, 1458 | "engines": { 1459 | "node": ">= 0.4" 1460 | } 1461 | }, 1462 | "node_modules/setprototypeof": { 1463 | "version": "1.2.0", 1464 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1465 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1466 | }, 1467 | "node_modules/shebang-command": { 1468 | "version": "2.0.0", 1469 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1470 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1471 | "dev": true, 1472 | "dependencies": { 1473 | "shebang-regex": "^3.0.0" 1474 | }, 1475 | "engines": { 1476 | "node": ">=8" 1477 | } 1478 | }, 1479 | "node_modules/shebang-regex": { 1480 | "version": "3.0.0", 1481 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1482 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1483 | "dev": true, 1484 | "engines": { 1485 | "node": ">=8" 1486 | } 1487 | }, 1488 | "node_modules/side-channel": { 1489 | "version": "1.0.6", 1490 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 1491 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 1492 | "dependencies": { 1493 | "call-bind": "^1.0.7", 1494 | "es-errors": "^1.3.0", 1495 | "get-intrinsic": "^1.2.4", 1496 | "object-inspect": "^1.13.1" 1497 | }, 1498 | "engines": { 1499 | "node": ">= 0.4" 1500 | }, 1501 | "funding": { 1502 | "url": "https://github.com/sponsors/ljharb" 1503 | } 1504 | }, 1505 | "node_modules/signal-exit": { 1506 | "version": "4.1.0", 1507 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 1508 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 1509 | "dev": true, 1510 | "engines": { 1511 | "node": ">=14" 1512 | }, 1513 | "funding": { 1514 | "url": "https://github.com/sponsors/isaacs" 1515 | } 1516 | }, 1517 | "node_modules/smart-buffer": { 1518 | "version": "4.2.0", 1519 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", 1520 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", 1521 | "engines": { 1522 | "node": ">= 6.0.0", 1523 | "npm": ">= 3.0.0" 1524 | } 1525 | }, 1526 | "node_modules/socks": { 1527 | "version": "2.3.3", 1528 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", 1529 | "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", 1530 | "dependencies": { 1531 | "ip": "1.1.5", 1532 | "smart-buffer": "^4.1.0" 1533 | }, 1534 | "engines": { 1535 | "node": ">= 6.0.0", 1536 | "npm": ">= 3.0.0" 1537 | } 1538 | }, 1539 | "node_modules/socks-proxy-agent": { 1540 | "version": "4.0.2", 1541 | "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", 1542 | "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", 1543 | "dependencies": { 1544 | "agent-base": "~4.2.1", 1545 | "socks": "~2.3.2" 1546 | }, 1547 | "engines": { 1548 | "node": ">= 6" 1549 | } 1550 | }, 1551 | "node_modules/socks-proxy-agent/node_modules/agent-base": { 1552 | "version": "4.2.1", 1553 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", 1554 | "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", 1555 | "dependencies": { 1556 | "es6-promisify": "^5.0.0" 1557 | }, 1558 | "engines": { 1559 | "node": ">= 4.0.0" 1560 | } 1561 | }, 1562 | "node_modules/socks/node_modules/ip": { 1563 | "version": "1.1.5", 1564 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", 1565 | "integrity": "sha512-rBtCAQAJm8A110nbwn6YdveUnuZH3WrC36IwkRXxDnq53JvXA2NVQvB7IHyKomxK1MJ4VDNw3UtFDdXQ+AvLYA==" 1566 | }, 1567 | "node_modules/source-map": { 1568 | "version": "0.6.1", 1569 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1570 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1571 | "optional": true, 1572 | "engines": { 1573 | "node": ">=0.10.0" 1574 | } 1575 | }, 1576 | "node_modules/statuses": { 1577 | "version": "2.0.1", 1578 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1579 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1580 | "engines": { 1581 | "node": ">= 0.8" 1582 | } 1583 | }, 1584 | "node_modules/string_decoder": { 1585 | "version": "1.1.1", 1586 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1587 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1588 | "dependencies": { 1589 | "safe-buffer": "~5.1.0" 1590 | } 1591 | }, 1592 | "node_modules/string_decoder/node_modules/safe-buffer": { 1593 | "version": "5.1.2", 1594 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1595 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1596 | }, 1597 | "node_modules/string-width": { 1598 | "version": "5.1.2", 1599 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 1600 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 1601 | "dev": true, 1602 | "dependencies": { 1603 | "eastasianwidth": "^0.2.0", 1604 | "emoji-regex": "^9.2.2", 1605 | "strip-ansi": "^7.0.1" 1606 | }, 1607 | "engines": { 1608 | "node": ">=12" 1609 | }, 1610 | "funding": { 1611 | "url": "https://github.com/sponsors/sindresorhus" 1612 | } 1613 | }, 1614 | "node_modules/string-width-cjs": { 1615 | "name": "string-width", 1616 | "version": "4.2.3", 1617 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1618 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1619 | "dev": true, 1620 | "dependencies": { 1621 | "emoji-regex": "^8.0.0", 1622 | "is-fullwidth-code-point": "^3.0.0", 1623 | "strip-ansi": "^6.0.1" 1624 | }, 1625 | "engines": { 1626 | "node": ">=8" 1627 | } 1628 | }, 1629 | "node_modules/string-width-cjs/node_modules/ansi-regex": { 1630 | "version": "5.0.1", 1631 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1632 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1633 | "dev": true, 1634 | "engines": { 1635 | "node": ">=8" 1636 | } 1637 | }, 1638 | "node_modules/string-width-cjs/node_modules/emoji-regex": { 1639 | "version": "8.0.0", 1640 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1641 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1642 | "dev": true 1643 | }, 1644 | "node_modules/string-width-cjs/node_modules/strip-ansi": { 1645 | "version": "6.0.1", 1646 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1647 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1648 | "dev": true, 1649 | "dependencies": { 1650 | "ansi-regex": "^5.0.1" 1651 | }, 1652 | "engines": { 1653 | "node": ">=8" 1654 | } 1655 | }, 1656 | "node_modules/strip-ansi": { 1657 | "version": "7.1.0", 1658 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 1659 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 1660 | "dev": true, 1661 | "dependencies": { 1662 | "ansi-regex": "^6.0.1" 1663 | }, 1664 | "engines": { 1665 | "node": ">=12" 1666 | }, 1667 | "funding": { 1668 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 1669 | } 1670 | }, 1671 | "node_modules/strip-ansi-cjs": { 1672 | "name": "strip-ansi", 1673 | "version": "6.0.1", 1674 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1675 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1676 | "dev": true, 1677 | "dependencies": { 1678 | "ansi-regex": "^5.0.1" 1679 | }, 1680 | "engines": { 1681 | "node": ">=8" 1682 | } 1683 | }, 1684 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { 1685 | "version": "5.0.1", 1686 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1687 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1688 | "dev": true, 1689 | "engines": { 1690 | "node": ">=8" 1691 | } 1692 | }, 1693 | "node_modules/thunkify": { 1694 | "version": "2.1.2", 1695 | "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", 1696 | "integrity": "sha512-w9foI80XcGImrhMQ19pxunaEC5Rp2uzxZZg4XBAFRfiLOplk3F0l7wo+bO16vC2/nlQfR/mXZxcduo0MF2GWLg==" 1697 | }, 1698 | "node_modules/toidentifier": { 1699 | "version": "1.0.1", 1700 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1701 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1702 | "engines": { 1703 | "node": ">=0.6" 1704 | } 1705 | }, 1706 | "node_modules/ts-node": { 1707 | "version": "10.9.2", 1708 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", 1709 | "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", 1710 | "dev": true, 1711 | "dependencies": { 1712 | "@cspotcode/source-map-support": "^0.8.0", 1713 | "@tsconfig/node10": "^1.0.7", 1714 | "@tsconfig/node12": "^1.0.7", 1715 | "@tsconfig/node14": "^1.0.0", 1716 | "@tsconfig/node16": "^1.0.2", 1717 | "acorn": "^8.4.1", 1718 | "acorn-walk": "^8.1.1", 1719 | "arg": "^4.1.0", 1720 | "create-require": "^1.1.0", 1721 | "diff": "^4.0.1", 1722 | "make-error": "^1.1.1", 1723 | "v8-compile-cache-lib": "^3.0.1", 1724 | "yn": "3.1.1" 1725 | }, 1726 | "bin": { 1727 | "ts-node": "dist/bin.js", 1728 | "ts-node-cwd": "dist/bin-cwd.js", 1729 | "ts-node-esm": "dist/bin-esm.js", 1730 | "ts-node-script": "dist/bin-script.js", 1731 | "ts-node-transpile-only": "dist/bin-transpile.js", 1732 | "ts-script": "dist/bin-script-deprecated.js" 1733 | }, 1734 | "peerDependencies": { 1735 | "@swc/core": ">=1.2.50", 1736 | "@swc/wasm": ">=1.2.50", 1737 | "@types/node": "*", 1738 | "typescript": ">=2.7" 1739 | }, 1740 | "peerDependenciesMeta": { 1741 | "@swc/core": { 1742 | "optional": true 1743 | }, 1744 | "@swc/wasm": { 1745 | "optional": true 1746 | } 1747 | } 1748 | }, 1749 | "node_modules/tslib": { 1750 | "version": "2.6.2", 1751 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", 1752 | "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" 1753 | }, 1754 | "node_modules/type-check": { 1755 | "version": "0.3.2", 1756 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 1757 | "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", 1758 | "dependencies": { 1759 | "prelude-ls": "~1.1.2" 1760 | }, 1761 | "engines": { 1762 | "node": ">= 0.8.0" 1763 | } 1764 | }, 1765 | "node_modules/type-is": { 1766 | "version": "1.6.18", 1767 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1768 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1769 | "dependencies": { 1770 | "media-typer": "0.3.0", 1771 | "mime-types": "~2.1.24" 1772 | }, 1773 | "engines": { 1774 | "node": ">= 0.6" 1775 | } 1776 | }, 1777 | "node_modules/typescript": { 1778 | "version": "5.4.5", 1779 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", 1780 | "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", 1781 | "dev": true, 1782 | "bin": { 1783 | "tsc": "bin/tsc", 1784 | "tsserver": "bin/tsserver" 1785 | }, 1786 | "engines": { 1787 | "node": ">=14.17" 1788 | } 1789 | }, 1790 | "node_modules/undici-types": { 1791 | "version": "5.26.5", 1792 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 1793 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 1794 | "dev": true 1795 | }, 1796 | "node_modules/unpipe": { 1797 | "version": "1.0.0", 1798 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1799 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1800 | "engines": { 1801 | "node": ">= 0.8" 1802 | } 1803 | }, 1804 | "node_modules/util-deprecate": { 1805 | "version": "1.0.2", 1806 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1807 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 1808 | }, 1809 | "node_modules/utils-merge": { 1810 | "version": "1.0.1", 1811 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1812 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1813 | "engines": { 1814 | "node": ">= 0.4.0" 1815 | } 1816 | }, 1817 | "node_modules/v8-compile-cache-lib": { 1818 | "version": "3.0.1", 1819 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 1820 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 1821 | "dev": true 1822 | }, 1823 | "node_modules/vary": { 1824 | "version": "1.1.2", 1825 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1826 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1827 | "engines": { 1828 | "node": ">= 0.8" 1829 | } 1830 | }, 1831 | "node_modules/which": { 1832 | "version": "2.0.2", 1833 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1834 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1835 | "dev": true, 1836 | "dependencies": { 1837 | "isexe": "^2.0.0" 1838 | }, 1839 | "bin": { 1840 | "node-which": "bin/node-which" 1841 | }, 1842 | "engines": { 1843 | "node": ">= 8" 1844 | } 1845 | }, 1846 | "node_modules/word-wrap": { 1847 | "version": "1.2.5", 1848 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 1849 | "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 1850 | "engines": { 1851 | "node": ">=0.10.0" 1852 | } 1853 | }, 1854 | "node_modules/wrap-ansi": { 1855 | "version": "8.1.0", 1856 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 1857 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 1858 | "dev": true, 1859 | "dependencies": { 1860 | "ansi-styles": "^6.1.0", 1861 | "string-width": "^5.0.1", 1862 | "strip-ansi": "^7.0.1" 1863 | }, 1864 | "engines": { 1865 | "node": ">=12" 1866 | }, 1867 | "funding": { 1868 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1869 | } 1870 | }, 1871 | "node_modules/wrap-ansi-cjs": { 1872 | "name": "wrap-ansi", 1873 | "version": "7.0.0", 1874 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1875 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1876 | "dev": true, 1877 | "dependencies": { 1878 | "ansi-styles": "^4.0.0", 1879 | "string-width": "^4.1.0", 1880 | "strip-ansi": "^6.0.0" 1881 | }, 1882 | "engines": { 1883 | "node": ">=10" 1884 | }, 1885 | "funding": { 1886 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1887 | } 1888 | }, 1889 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { 1890 | "version": "5.0.1", 1891 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1892 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1893 | "dev": true, 1894 | "engines": { 1895 | "node": ">=8" 1896 | } 1897 | }, 1898 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 1899 | "version": "4.3.0", 1900 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1901 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1902 | "dev": true, 1903 | "dependencies": { 1904 | "color-convert": "^2.0.1" 1905 | }, 1906 | "engines": { 1907 | "node": ">=8" 1908 | }, 1909 | "funding": { 1910 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1911 | } 1912 | }, 1913 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 1914 | "version": "8.0.0", 1915 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1916 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1917 | "dev": true 1918 | }, 1919 | "node_modules/wrap-ansi-cjs/node_modules/string-width": { 1920 | "version": "4.2.3", 1921 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1922 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1923 | "dev": true, 1924 | "dependencies": { 1925 | "emoji-regex": "^8.0.0", 1926 | "is-fullwidth-code-point": "^3.0.0", 1927 | "strip-ansi": "^6.0.1" 1928 | }, 1929 | "engines": { 1930 | "node": ">=8" 1931 | } 1932 | }, 1933 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 1934 | "version": "6.0.1", 1935 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1936 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1937 | "dev": true, 1938 | "dependencies": { 1939 | "ansi-regex": "^5.0.1" 1940 | }, 1941 | "engines": { 1942 | "node": ">=8" 1943 | } 1944 | }, 1945 | "node_modules/xregexp": { 1946 | "version": "2.0.0", 1947 | "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", 1948 | "integrity": "sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==", 1949 | "engines": { 1950 | "node": "*" 1951 | } 1952 | }, 1953 | "node_modules/yallist": { 1954 | "version": "3.1.1", 1955 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1956 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1957 | }, 1958 | "node_modules/yn": { 1959 | "version": "3.1.1", 1960 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 1961 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 1962 | "dev": true, 1963 | "engines": { 1964 | "node": ">=6" 1965 | } 1966 | } 1967 | } 1968 | } 1969 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authentication-flows-js", 3 | "version": "1.12.3", 4 | "description": "authentication-flows for javascript", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "prebuild": "rimraf dist", 8 | "build": "tsc", 9 | "test": "npx tsc --target ES6" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+ssh://git@github.com/OhadR/authentication-flows-js.git" 14 | }, 15 | "keywords": [ 16 | "authentication", 17 | "create account", 18 | "forgot password", 19 | "account management", 20 | "login", 21 | "logout" 22 | ], 23 | "author": "Ohad Redlich", 24 | "license": "ISC", 25 | "dependencies": { 26 | "@log4js-node/log4js-api": "^1.0.2", 27 | "debug": "^4.3.1", 28 | "express": "^4.18.2", 29 | "nodemailer": "4.7.0", 30 | "proxy-agent": "^3.1.1" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^20.8.0", 34 | "rimraf": "^5.0.7", 35 | "ts-node": "^10.9.1", 36 | "typescript": "^5.2.2" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/OhadR/authentication-flows-js/issues" 40 | }, 41 | "homepage": "https://github.com/OhadR/authentication-flows-js#readme" 42 | } 43 | -------------------------------------------------------------------------------- /src/config/password-policy-repository-impl.ts: -------------------------------------------------------------------------------- 1 | import { PasswordPolicyRepository } from "../interfaces/password-policy-repository"; 2 | import { AuthenticationPolicy } from "../types/authentication-policy"; 3 | 4 | //load the json file with the policy: 5 | const config = require('../../config/authentication-policy-repository-config.json'); 6 | 7 | export class PasswordPolicyRepositoryImpl implements PasswordPolicyRepository { 8 | public getAuthenticationPolicy(settingsId: number): AuthenticationPolicy { 9 | const policy: AuthenticationPolicy = new AuthenticationPolicy( 10 | config.passwordMinLength, 11 | config.passwordMaxLength, 12 | config.passwordMinUpCaseChars, 13 | config.passwordMinLoCaseChars, 14 | config.passwordMinNumbericDigits, 15 | config.passwordMinSpecialSymbols, 16 | config.passwordBlackList, 17 | config.maxPasswordEntryAttempts, 18 | config.passwordLifeInDays); 19 | return policy; 20 | } 21 | 22 | public getDefaultAuthenticationPolicy(): AuthenticationPolicy { 23 | return this.getAuthenticationPolicy(0); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/core/authentication-flows-processor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AccountLockedError, 3 | AuthenticationFlowsError, 4 | LinkExpiredError, 5 | PasswordAlreadyChangedError, 6 | AuthenticationPolicy, 7 | } from ".."; 8 | import { CreateAccountInterceptor } from "../interceptors/create-account-interceptor"; 9 | import { DefaultMailSenderImpl } from "../interceptors/default-email-sender"; 10 | import { 11 | ACTIVATE_ACCOUNT_ENDPOINT, 12 | AUTHENTICATION_MAIL_SUBJECT, 13 | PASSWORD_CHANGED_MAIL_SUBJECT, 14 | RESTORE_PASSWORD_ENDPOINT, 15 | RESTORE_PASSWORD_MAIL_SUBJECT, 16 | UNLOCK_MAIL_SUBJECT, 17 | } from "../types/flows-constatns"; 18 | import { AuthenticationAccountRepository } from "../interfaces/repository/authentication-account-repository"; 19 | import { PasswordPolicyRepository } from "../interfaces/password-policy-repository"; 20 | import { AuthenticationUser } from "../interfaces/authentication-user"; 21 | import { MailSender } from "../interfaces/mail-sender"; 22 | import { AuthenticationUserImpl } from "./authentication-user-impl"; 23 | import { PasswordPolicyRepositoryImpl } from "../config/password-policy-repository-impl"; 24 | import { randomString, shaString } from "../crypto/key-generator"; 25 | 26 | const debug = require('debug')('authentication-flows:processor'); 27 | 28 | //constants that are relevant only for this class: 29 | const EMAIL_NOT_VALID = "The e-mail you have entered is not valid."; 30 | const USER_ALREADY_EXIST = "cannot create account - user already exist."; 31 | 32 | const PASSWORD_CANNOT_BE_USED = "Your password is not acceptable by the organizational password policy."; 33 | const PASSWORD_IS_TOO_LONG = "Password is too long"; 34 | const PASSWORD_IS_TOO_SHORT = "Password is too short"; 35 | const PASSWORD_TOO_FEW_LOWERS = "Password needs to contains at least XXXX lower-case characters"; 36 | const PASSWORD_TOO_FEW_UPPERS = "Password needs to contains at least XXXX upper-case characters"; 37 | const PASSWORD_TOO_FEW_NUMERICS = "Password needs to contains at least XXXX numeric characters"; 38 | const PASSWORD_TOO_FEW_SPECIAL_SYMBOLS = "Password needs to contains at least XXXX special symbols"; 39 | const SETTING_A_NEW_PASSWORD_HAS_FAILED_PLEASE_NOTE_THE_PASSWORD_POLICY_AND_TRY_AGAIN_ERROR_MESSAGE = 40 | "Setting a new password has failed. Please note the password policy and try again. Error message: "; 41 | const ACCOUNT_CREATION_HAS_FAILED_PASSWORDS_DO_NOT_MATCH = 42 | "Account creation has failed. These passwords don't match"; 43 | 44 | const ACCOUNT_LOCKED_OR_DOES_NOT_EXIST = "Account is locked or does not exist"; 45 | 46 | const LINK_HAS_EXPIRED = "link has expired"; 47 | const LINK_DOES_NOT_EXIST = "link does not exist in DB"; //means that link was already used, or it is invalid 48 | 49 | const CHANGE_PASSWORD_FAILED_NEW_PASSWORD_SAME_AS_OLD_PASSWORD = "CHANGE PASSWORD FAILED: New Password is same as Old Password."; 50 | const CHANGE_PASSWORD_BAD_OLD_PASSWORD = "CHANGE PASSWORD Failed: Bad Old Password."; 51 | 52 | export class AuthenticationFlowsProcessor { 53 | 54 | private static _instance: AuthenticationFlowsProcessor; 55 | 56 | private _createAccountEndpoint: CreateAccountInterceptor = new CreateAccountInterceptor(); 57 | 58 | private _authenticationAccountRepository: AuthenticationAccountRepository; 59 | private _applicationName: string; 60 | private _passwordPolicyRepository: PasswordPolicyRepository; 61 | 62 | private _mailSender: MailSender = new DefaultMailSenderImpl(); 63 | 64 | 65 | private constructor() { 66 | this._passwordPolicyRepository = new PasswordPolicyRepositoryImpl(); 67 | } 68 | 69 | public static get instance() { 70 | return this._instance || (this._instance = new this()); 71 | } 72 | 73 | public set authenticationAccountRepository(authenticationAccountRepository: AuthenticationAccountRepository) { 74 | debug(`set authenticationAccountRepository: ${authenticationAccountRepository.constructor.name}`); 75 | this._authenticationAccountRepository = authenticationAccountRepository; 76 | } 77 | 78 | public set applicationName(applicationName: string) { 79 | if(!applicationName) 80 | throw new Error('applicationName cannot be null.'); 81 | 82 | debug(`set applicationName: ${applicationName}`); 83 | this._applicationName = applicationName; 84 | } 85 | 86 | public set mailSender(mailSender: MailSender) { 87 | debug(`set mailSender: ${JSON.stringify(mailSender)}`); 88 | this._mailSender = mailSender; 89 | } 90 | 91 | public set createAccountInterceptor(createAccountInterceptor: CreateAccountInterceptor) { 92 | if(!createAccountInterceptor) 93 | return; 94 | 95 | debug(`set createAccountInterceptor`); 96 | this._createAccountEndpoint = createAccountInterceptor; 97 | } 98 | 99 | async authenticate( 100 | name: string, 101 | pass: string, 102 | serverPath: string) { 103 | debug(`authenticating ${name}...`); 104 | 105 | const hashedPass = shaString(pass); 106 | 107 | const user: AuthenticationUser = await this._authenticationAccountRepository.loadUserByUsername(name); 108 | // query the db for the given username 109 | if (!user) 110 | throw new Error('cannot find user'); 111 | 112 | if(!user.isEnabled()) 113 | throw new Error('account is not active'); 114 | 115 | 116 | //validate the credentials: 117 | if(hashedPass !== user.getPassword()) { 118 | //wrong password: 119 | await this.onAuthenticationFailure(user, serverPath); 120 | } 121 | 122 | const passChangeRequired = await this.setLoginSuccessForUser(name); 123 | 124 | //success 125 | return user; 126 | } 127 | 128 | async createAccount(email: string, password: string, retypedPassword: string, firstName: string, lastName: string, serverPath: string) { 129 | //validate the input: 130 | AuthenticationFlowsProcessor.validateEmail(email); 131 | 132 | this.validatePassword(password); 133 | 134 | AuthenticationFlowsProcessor.validateRetypedPassword(password, retypedPassword); 135 | 136 | //encrypt the password: 137 | const encodedPassword: string = shaString(password); 138 | 139 | //make any other additional chackes. this let applications override this impl and add their custom functionality: 140 | this._createAccountEndpoint.additionalValidations(email, password); 141 | 142 | email = email.toLowerCase(); // issue #23 : username is case-sensitive 143 | debug('createAccount() for user ' + email); 144 | debug('encoded password: ' + encodedPassword); 145 | 146 | let authUser: AuthenticationUser = null; 147 | try 148 | { 149 | authUser = await this._authenticationAccountRepository.loadUserByUsername( email ); 150 | } 151 | catch(unfe) 152 | { 153 | //basically do nothing - we expect user not to be found. 154 | } 155 | debug(`create-account, user: ${authUser}`); 156 | 157 | //if user exist, but not activated - we allow re-registration: 158 | if(authUser) 159 | { 160 | if( !authUser.isEnabled()) 161 | { 162 | await this._authenticationAccountRepository.deleteUser( email ); 163 | } 164 | else 165 | { 166 | //error - user already exists and active 167 | //log.error( "cannot create account - user " + email + " already exist." ); 168 | debug( "cannot create account - user " + email + " already exist." ); 169 | throw new AuthenticationFlowsError( USER_ALREADY_EXIST ); 170 | } 171 | } 172 | 173 | const authorities: string[] = this.setAuthorities(); //set authorities 174 | authUser = new AuthenticationUserImpl( 175 | email, encodedPassword, 176 | false, //start as de-activated 177 | this._passwordPolicyRepository.getDefaultAuthenticationPolicy().getMaxPasswordEntryAttempts(), 178 | null, //set by the repo-impl 179 | firstName, 180 | lastName, 181 | authorities); 182 | 183 | debug(`authUser: ${authUser}`); 184 | 185 | await this._authenticationAccountRepository.createUser(authUser); 186 | 187 | await this._createAccountEndpoint.postCreateAccount( email ); 188 | 189 | const token: string = randomString(); 190 | const activationUrl: string = serverPath + ACTIVATE_ACCOUNT_ENDPOINT + 191 | "/" + token; 192 | //persist the "uts", so this activation link will be single-used: 193 | await this._authenticationAccountRepository.addLink( email, token ); 194 | 195 | 196 | //debug("sending registration email to " + email + "; activationUrl: " + activationUrl); 197 | debug("sending registration email; activationUrl: " + activationUrl); 198 | 199 | 200 | await this._mailSender.sendEmail(email, 201 | this._applicationName + AUTHENTICATION_MAIL_SUBJECT, 202 | activationUrl ); 203 | } 204 | 205 | public async activateAccount(linkCode: string) { 206 | 207 | const username: string = await this._authenticationAccountRepository.getUsernameByLink(linkCode); 208 | debug(`activating username: ${username}`); 209 | 210 | // check token expiration and throw LINK_HAS_EXPIRED 211 | const tokenData = await this._authenticationAccountRepository.getLink(username); 212 | 213 | if(!tokenData || !tokenData.link) { 214 | debug(`ERROR: user ${username} tried to use a non-existing link`); 215 | throw new LinkExpiredError(`ERROR: user ${username} tried to use non-existing link`); 216 | } 217 | 218 | const tokenDate: Date = new Date(tokenData.date); 219 | 220 | //check if link is expired: 221 | if(new Date().getTime() - tokenDate.getTime() > 1000 * 1000) { 222 | debug(`ERROR: user ${username} tried to use an expired link`); 223 | throw new LinkExpiredError(`ERROR: user ${username} tried to use an expired link: link is valid for 1000 seconds`); 224 | } 225 | 226 | //this part was persisted in the DB, in order to make sure the activation-link is single-used. 227 | //so here we remove it from the DB: 228 | await this.removeLinkFromDB( username ); 229 | 230 | // enable the account. NOTE: if userEmail was not found in DB, we will get RuntimeException (NoSuchElement) 231 | await this.setEnabled(username); 232 | 233 | // reset the #attempts, since there is a flow of exceeding attempts number, so when clicking the link 234 | // (in the email), we get here and enable the account and reset the attempts number 235 | await this.setLoginSuccessForUser(username); 236 | } 237 | 238 | /** 239 | * decode/decrypt the (UTS part of the) link received. ensure it was not expired, 240 | * and that the password was not changed in between 241 | * @param link 242 | */ 243 | public async validatePasswordRestoration(link: string) { 244 | 245 | const username: string = await this._authenticationAccountRepository.getUsernameByLink(link); 246 | debug(`restore password for username: ${username}`); 247 | 248 | const lastChange: Date = await this._authenticationAccountRepository.getPasswordLastChangeDate(username); 249 | const lastChangedDate: Date = new Date(lastChange); 250 | debug("lastChangedDate: " + lastChangedDate); 251 | 252 | const tokenData = await this._authenticationAccountRepository.getLink(username); 253 | 254 | if(!tokenData || !tokenData.link) { 255 | debug(`ERROR: user ${username} tried to use a non-existing link`); 256 | throw new LinkExpiredError(`ERROR: user ${username} tried to use non-existing link`); 257 | } 258 | 259 | const tokenDate: Date = new Date(tokenData.date); 260 | 261 | //check if link is expired: 262 | if(new Date().getTime() - tokenDate.getTime() > 1000 * 1000) { 263 | debug(`ERROR: user ${username} tried to use an expired link`); 264 | throw new LinkExpiredError(`ERROR: user ${username} tried to use an expired link: link is valid for 1000 seconds`); 265 | } 266 | 267 | //if password was changed AFTER the email creation (that is AFTER the user initiated "4got password" flow) - 268 | //it means the request is irrelevant 269 | if(lastChangedDate > tokenDate) { 270 | debug(`ERROR: user ${username} tried to use an expired link: password was already changed AFTER the timestamp of the link`); 271 | throw new PasswordAlreadyChangedError(`ERROR: user ${username} tried to use an expired link: password was already changed AFTER the timestamp of the link`); 272 | } 273 | } 274 | 275 | getAuthenticationSettings(): AuthenticationPolicy { 276 | return this._passwordPolicyRepository.getDefaultAuthenticationPolicy(); 277 | } 278 | 279 | 280 | async forgotPassword(email: string, serverPath: string) { 281 | debug('forgotPassword() for user ' + email); 282 | 283 | AuthenticationFlowsProcessor.validateEmail(email); 284 | 285 | //if account is already locked, he is not allowed to reset password: 286 | if( ! await this._authenticationAccountRepository.isEnabled(email) ) 287 | { 288 | //security bug: Even if we don’t find an email address, we return 'ok'. We don’t want untoward 289 | // bots figuring out what emails are real vs not real in our database. 290 | //throw new Error( ACCOUNT_LOCKED_OR_DOES_NOT_EXIST ); 291 | return; 292 | } 293 | 294 | await this.sendPasswordRestoreMail(email, serverPath); 295 | } 296 | 297 | private async removeLinkFromDB(username: string) { 298 | const deleted = await this._authenticationAccountRepository.removeLink(username); 299 | if(!deleted) 300 | throw new Error(LINK_DOES_NOT_EXIST); 301 | } 302 | 303 | private async sendUnlockAccountMail(email: string, serverPath: string) { 304 | 305 | const token: string = randomString(); 306 | const activationUrl: string = serverPath + ACTIVATE_ACCOUNT_ENDPOINT + 307 | "/" + token; 308 | //persist the "uts", so this activation link will be single-used: 309 | await this._authenticationAccountRepository.addLink( email, token ); 310 | 311 | debug(`sending Unlock-Account email to ${email}; activationUrl: ${activationUrl}`); 312 | 313 | await this._mailSender.sendEmail(email, 314 | this._applicationName + UNLOCK_MAIL_SUBJECT, 315 | activationUrl ); 316 | } 317 | 318 | private async setEnabled(userEmail: string) { 319 | await this._authenticationAccountRepository.setEnabled(userEmail); 320 | } 321 | 322 | private async setLoginSuccessForUser(username: string): Promise { 323 | debug("setting login success for user " + username); 324 | 325 | await this._authenticationAccountRepository.setAttemptsLeft( username, 326 | this.getAuthenticationSettings().getMaxPasswordEntryAttempts() ); 327 | 328 | await this._authenticationAccountRepository.setLastLoginDate( username, new Date() ); 329 | 330 | return this.isPasswordChangeRequired(username); 331 | } 332 | 333 | 334 | private static validateEmail(email: string) { 335 | if( ! email.includes("@") ) { 336 | throw new AuthenticationFlowsError( EMAIL_NOT_VALID ); 337 | } 338 | } 339 | 340 | private static validateRetypedPassword(password: string, retypedPassword: string) { 341 | if(password !== retypedPassword) { 342 | throw new AuthenticationFlowsError( ACCOUNT_CREATION_HAS_FAILED_PASSWORDS_DO_NOT_MATCH ); 343 | } 344 | } 345 | 346 | 347 | private validatePassword(password: string) { 348 | const settings: AuthenticationPolicy = this._passwordPolicyRepository.getDefaultAuthenticationPolicy(); 349 | 350 | const blackList: string[] = settings.getPasswordBlackList(); 351 | if(blackList != null) { 352 | for(const forbidenPswd of blackList) { 353 | if(password.toLowerCase() === forbidenPswd.toLowerCase()) 354 | { 355 | throw new Error(SETTING_A_NEW_PASSWORD_HAS_FAILED_PLEASE_NOTE_THE_PASSWORD_POLICY_AND_TRY_AGAIN_ERROR_MESSAGE + " " + PASSWORD_CANNOT_BE_USED); 356 | } 357 | } 358 | } 359 | 360 | 361 | if(password.length > settings.getPasswordMaxLength()) 362 | throw new Error(SETTING_A_NEW_PASSWORD_HAS_FAILED_PLEASE_NOTE_THE_PASSWORD_POLICY_AND_TRY_AGAIN_ERROR_MESSAGE + " " + PASSWORD_IS_TOO_LONG); 363 | 364 | 365 | if(password.length < settings.getPasswordMinLength()) 366 | throw new Error(SETTING_A_NEW_PASSWORD_HAS_FAILED_PLEASE_NOTE_THE_PASSWORD_POLICY_AND_TRY_AGAIN_ERROR_MESSAGE + " " + PASSWORD_IS_TOO_SHORT); 367 | 368 | let uppersCounter = 0; 369 | let lowersCounter = 0; 370 | let numericCounter = 0; 371 | let specialSymbolCounter = 0; 372 | 373 | for(let i=0; i= '0' && c <= '9') &&(c >='A' && c <= 'Z')) 377 | ++uppersCounter; 378 | else if (c == c.toLowerCase() && !(c >= '0' && c <= '9') &&(c >='a' && c <= 'z')) 379 | ++lowersCounter; 380 | else if (c >= '0' && c <= '9') 381 | ++numericCounter; 382 | else 383 | ++specialSymbolCounter; 384 | } 385 | 386 | let retVal; 387 | if(uppersCounter < settings.getPasswordMinUpCaseChars()) { 388 | retVal = PASSWORD_TOO_FEW_UPPERS.replace('XXXX', settings.getPasswordMinUpCaseChars() + ''); 389 | } 390 | if(lowersCounter < settings.getPasswordMinLoCaseChars()) { 391 | retVal = PASSWORD_TOO_FEW_LOWERS.replace('XXXX', settings.getPasswordMinLoCaseChars() + ''); 392 | } 393 | if(numericCounter < settings.getPasswordMinNumbericDigits()) { 394 | retVal = PASSWORD_TOO_FEW_NUMERICS.replace('XXXX', settings.getPasswordMinNumbericDigits() + ''); 395 | } 396 | if(specialSymbolCounter < settings.getPasswordMinSpecialSymbols()) { 397 | retVal = PASSWORD_TOO_FEW_SPECIAL_SYMBOLS.replace('XXXX', settings.getPasswordMinSpecialSymbols()+ ''); 398 | } 399 | 400 | if(retVal) 401 | throw new Error(SETTING_A_NEW_PASSWORD_HAS_FAILED_PLEASE_NOTE_THE_PASSWORD_POLICY_AND_TRY_AGAIN_ERROR_MESSAGE + " " + retVal); 402 | } 403 | 404 | private setAuthorities(): string[] 405 | { 406 | const set: string[] = []; 407 | set.push("ROLE_USER"); 408 | return set; 409 | } 410 | 411 | 412 | private async isPasswordChangeRequired(username: string): Promise { 413 | const lastChange: Date = await this._authenticationAccountRepository.getPasswordLastChangeDate(username); 414 | const lastChangedDate: Date = new Date(lastChange); 415 | debug("lastChangedDate: " + lastChangedDate); 416 | debug("PasswordLifeInDays: " + this.getAuthenticationSettings().getPasswordLifeInDays()); 417 | 418 | const passwordChangeRequired = (Date.now() - lastChangedDate.getTime()) > (this.getAuthenticationSettings().getPasswordLifeInDays() * 24 * 60 * 60 * 1000); 419 | debug("passwordChangeRequired: " + passwordChangeRequired); 420 | return passwordChangeRequired; 421 | } 422 | 423 | async deleteAccount(email: string, password: string) { 424 | if(!password) 425 | return; 426 | 427 | //encrypt the password: 428 | const encodedPassword: string = shaString(password); 429 | 430 | //validate the credentials: 431 | if(encodedPassword !== await this._authenticationAccountRepository.getEncodedPassword(email)) 432 | throw new Error('bad credentials'); 433 | 434 | //delete: 435 | debug("deleting account " + email); 436 | await this._authenticationAccountRepository.deleteUser( email ); 437 | } 438 | 439 | private async sendPasswordRestoreMail(email: string, serverPath: string) { 440 | const token: string = randomString(); 441 | const passwordRestoreUrl: string = serverPath + RESTORE_PASSWORD_ENDPOINT + 442 | "/" + token; 443 | //persist the "uts", so this activation link will be single-used: 444 | await this._authenticationAccountRepository.addLink( email, token ); 445 | 446 | debug("sending restore-password email to " + email + "; url: " + passwordRestoreUrl); 447 | 448 | await this._mailSender.sendEmail(email, 449 | this._applicationName + RESTORE_PASSWORD_MAIL_SUBJECT, 450 | passwordRestoreUrl ); 451 | } 452 | 453 | 454 | async setNewPassword(linkParam: string, password: string, retypedPassword: string) { 455 | //validate the input: 456 | AuthenticationFlowsProcessor.validateRetypedPassword(password, retypedPassword); 457 | 458 | this.validatePassword(password); 459 | 460 | //extract the username/email: 461 | const username: string = await this._authenticationAccountRepository.getUsernameByLink(linkParam); 462 | debug(`setNewPassword(): username: ${username}`); 463 | 464 | //validate expiration (again): 465 | const tokenData = await this._authenticationAccountRepository.getLink(username); 466 | 467 | if(!tokenData || !tokenData.link) { 468 | debug(`ERROR: user ${username} tried to use a non-existing link`); 469 | throw new LinkExpiredError(`ERROR: user ${username} tried to use non-existing link`); 470 | } 471 | 472 | const tokenDate: Date = new Date(tokenData.date); 473 | 474 | //check if link is expired: 475 | if(new Date().getTime() - tokenDate.getTime() > 1000 * 1000) { 476 | debug(`ERROR: user ${username} tried to use an expired link`); 477 | throw new LinkExpiredError(`ERROR: user ${username} tried to use an expired link: link is valid for 1000 seconds`); 478 | } 479 | 480 | 481 | //encrypt the password: 482 | const encodedPassword: string = shaString(password); 483 | 484 | //store the new password, and also clear the link, to ensure the activation-link is single-used: 485 | debug("setting password for user " + username); 486 | await this._authenticationAccountRepository.setPassword(username, encodedPassword); 487 | } 488 | 489 | 490 | private async onAuthenticationFailure( 491 | user: AuthenticationUser, 492 | serverPath: string) { 493 | 494 | if(!user) 495 | return; 496 | 497 | const username = user.getUsername(); 498 | debug(`login failed for user: ` + username); 499 | 500 | await this._authenticationAccountRepository.decrementAttemptsLeft(username); 501 | 502 | //if user is currently enabled, and num attempts left is 0, it means now we lock him up: 503 | if( user.isEnabled() && user.getLoginAttemptsLeft() == 0 ) { 504 | debug(`Account has been locked out for user: ${username} due to exceeding number of attempts to login.`); 505 | 506 | //lock the user 507 | await this._authenticationAccountRepository.setDisabled(username); 508 | 509 | await this.sendUnlockAccountMail(username, serverPath); 510 | throw new AccountLockedError(`Account has been locked out for user: ${username} due to exceeding number of attempts to login.`); 511 | } 512 | 513 | throw new Error('bad credentials'); 514 | } 515 | 516 | async changePassword( 517 | username: string, 518 | currentPassword: string, 519 | newPassword: string, 520 | retypedPassword: string) { 521 | 522 | AuthenticationFlowsProcessor.validateRetypedPassword(newPassword, retypedPassword); 523 | 524 | this.validatePassword(newPassword); 525 | 526 | //encrypt the password: 527 | const encodedPassword: string = shaString(newPassword); 528 | 529 | debug("setting password for user " + username); 530 | await this._authenticationAccountRepository.setPassword(username, encodedPassword); 531 | 532 | await this._mailSender.sendEmail(username, 533 | this._applicationName + PASSWORD_CHANGED_MAIL_SUBJECT, 534 | 'your password has been changed.' ); 535 | } 536 | 537 | async getAllUsers() : Promise { 538 | return this._authenticationAccountRepository.getAllUsers(); 539 | } 540 | 541 | async setAuthoritiesForUser(email: string, authorities: string[]) { 542 | return this._authenticationAccountRepository.setAuthorities(email, authorities); 543 | } 544 | } -------------------------------------------------------------------------------- /src/core/authentication-user-impl.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationUser } from "../interfaces/authentication-user"; 2 | 3 | export class AuthenticationUserImpl implements AuthenticationUser { 4 | constructor( 5 | private email: string, 6 | private encodedPassword: any, 7 | private isActive: boolean, 8 | private loginAttemptsLeft: number, 9 | private passwordLastChangeDate: Date, 10 | private firstName: string, 11 | private lastName: string, 12 | private authorities: string[], 13 | private token: string = null, 14 | private tokenDate: Date = null) { 15 | } 16 | 17 | getAuthorities(): string[] { 18 | return this.authorities; 19 | } 20 | 21 | getPassword(): string { 22 | return this.encodedPassword; 23 | } 24 | 25 | getUsername(): string { 26 | return this.email; 27 | } 28 | 29 | getFirstName(): string { 30 | return this.firstName; 31 | } 32 | 33 | getLastName(): string { 34 | return this.lastName; 35 | } 36 | 37 | getLoginAttemptsLeft(): number { 38 | return this.loginAttemptsLeft; 39 | } 40 | 41 | getPasswordLastChangeDate(): Date { 42 | return this.passwordLastChangeDate; 43 | } 44 | 45 | isEnabled(): boolean { 46 | return this.isActive; 47 | } 48 | 49 | getToken(): string { 50 | return this.token; 51 | } 52 | 53 | getTokenDate(): Date { 54 | return this.tokenDate; 55 | } 56 | } -------------------------------------------------------------------------------- /src/crypto/key-generator.ts: -------------------------------------------------------------------------------- 1 | // Node.js program to demonstrate the 2 | // crypto.publicEncrypt() method 3 | 4 | // Including crypto and fs module 5 | import * as crypto from 'crypto'; 6 | import * as fs from 'fs'; 7 | import * as path from "path"; 8 | const debug = require('debug')('authentication-flows:crypto'); 9 | 10 | const PUBLIC_KEY_FILE_NAME = "auth_flows_js_public_key"; 11 | const PRIVATE_KEY_FILE_NAME = "auth_flows_js_private_key"; 12 | 13 | export function generateKeyFile() { 14 | debug('generating key files...'); 15 | const keyPair = crypto.generateKeyPairSync('rsa', { 16 | modulusLength: 520, 17 | publicKeyEncoding: { 18 | type: 'pkcs1', 19 | format: 'pem' 20 | }, 21 | privateKeyEncoding: { 22 | type: 'pkcs1', 23 | format: 'pem', 24 | cipher: 'aes-256-cbc', 25 | passphrase: '' 26 | } 27 | }); 28 | 29 | // Creating public key file 30 | fs.writeFileSync(PUBLIC_KEY_FILE_NAME, keyPair.publicKey); 31 | fs.writeFileSync(PRIVATE_KEY_FILE_NAME, keyPair.privateKey); 32 | } 33 | 34 | /** 35 | * encrypt string and then encode-base64 36 | * @param plaintext 37 | */ 38 | export function encryptString (plaintext: string): string { 39 | return _encryptString(plaintext, path.join(".", PUBLIC_KEY_FILE_NAME)); 40 | } 41 | 42 | export function decryptString (encryptedText: string): string { 43 | return _decryptString(encryptedText, path.join(".", PRIVATE_KEY_FILE_NAME)); 44 | } 45 | 46 | export function shaString(data: string): string { 47 | return crypto 48 | .createHash('sha256') 49 | .update(data) 50 | .digest('base64'); 51 | } 52 | 53 | export function randomString(): string { 54 | return crypto.randomBytes(20).toString('hex'); 55 | } 56 | 57 | /** 58 | * encrypt string and then encode-base64 59 | * @param plaintext 60 | * @param publicKeyFile 61 | * @private 62 | */ 63 | function _encryptString (plaintext: string, publicKeyFile: string): string { 64 | const publicKey = fs.readFileSync(publicKeyFile, 'utf8'); 65 | 66 | // publicEncrypt() method with its parameters 67 | const encrypted = crypto.publicEncrypt( 68 | { 69 | key: publicKey, 70 | // padding: crypto.constants.RSA_PKCS1_OAEP_PADDING 71 | }, 72 | Buffer.from(plaintext, 'utf8')); 73 | const based64EncryptedContent: string = encrypted.toString('base64'); 74 | //debug(based64EncryptedContent); 75 | //instead of encodeURI(), just replace the '+' to '.': 76 | return based64EncryptedContent.replace(/\+/g, '.'); 77 | } 78 | 79 | function _decryptString (encryptedText: string, privateKeyFile): string { 80 | //first, instead of decodeURI(), just replace the '.' to '+': 81 | encryptedText = encryptedText.replace(/\./g, '+'); 82 | //debug(encryptedText); 83 | const privateKey = fs.readFileSync(privateKeyFile, 'utf8'); 84 | const decrypted = crypto.privateDecrypt( 85 | { 86 | key: privateKey.toString(), 87 | passphrase: '', 88 | // padding: crypto.constants.RSA_PKCS1_PADDING 89 | }, 90 | Buffer.from(encryptedText, 'base64')); 91 | return decrypted.toString("utf8"); 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/in-mem-impl/authentication-account-inmem-repository.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationAccountRepository, 2 | AuthenticationUser, 3 | AuthenticationUserImpl } from '..'; 4 | const debug = require('debug')('authentication-account-inmem-repository'); 5 | 6 | /** 7 | * IN-MEM implementation. used for debugging. initially was in a seperate package (https://github.com/OhadR/authentication-flows-js-inmem) 8 | * but for simplicity of dev,deployment and usage was bundled here. 9 | * Now when "app" wants in-mem impl, it does not have to import a different package, manage versions, etc. 10 | */ 11 | export class AuthenticationAccountInmemRepository implements AuthenticationAccountRepository { 12 | 13 | private users = new Map(); 14 | 15 | loadUserByUsername(username: string): Promise { 16 | return Promise.resolve( this.users.get(username) ); 17 | } 18 | 19 | setEnabled(username: string) { 20 | this.setEnabledFlag(username, true); 21 | } 22 | 23 | setDisabled(username: string) { 24 | this.setEnabledFlag(username, false); 25 | } 26 | 27 | protected async setEnabledFlag(username: string, flag: boolean) { 28 | 29 | const storedUser: AuthenticationUser = await this.loadUserByUsername(username); 30 | const newUser: AuthenticationUser = new AuthenticationUserImpl( 31 | username, 32 | storedUser.getPassword(), 33 | flag, 34 | storedUser.getLoginAttemptsLeft(), 35 | storedUser.getPasswordLastChangeDate(), 36 | storedUser.getFirstName(), 37 | storedUser.getLastName(), 38 | storedUser.getAuthorities(), 39 | storedUser.getToken(), 40 | storedUser.getTokenDate() 41 | ); 42 | 43 | //delete old user and set a new one, since iface does not support "setPassword()": 44 | this.deleteUser(username); 45 | this.users.set(username, newUser); 46 | } 47 | 48 | async isEnabled(username: string): Promise { 49 | const storedUser: AuthenticationUser = await this.loadUserByUsername(username); 50 | if (!storedUser) 51 | return false; 52 | return Promise.resolve( storedUser.isEnabled() ); 53 | } 54 | 55 | //TODO: should be in abstract class 56 | async decrementAttemptsLeft(username: string) { 57 | const storedUser: AuthenticationUser = await this.loadUserByUsername(username); 58 | let attempts = storedUser.getLoginAttemptsLeft(); 59 | debug(`current num attempts: ${attempts}`); 60 | await this.setAttemptsLeft(username, --attempts); 61 | } 62 | 63 | async setAttemptsLeft(username: string, numAttemptsAllowed: number) { 64 | const storedUser: AuthenticationUser = await this.loadUserByUsername(username); 65 | 66 | const newUser: AuthenticationUser = new AuthenticationUserImpl( 67 | username, 68 | storedUser.getPassword(), 69 | storedUser.isEnabled(), 70 | numAttemptsAllowed, 71 | storedUser.getPasswordLastChangeDate(), 72 | storedUser.getFirstName(), 73 | storedUser.getLastName(), 74 | storedUser.getAuthorities(), 75 | storedUser.getToken(), 76 | storedUser.getTokenDate() 77 | ); 78 | 79 | //delete old user and set a new one, since iface does not support "setPassword()": 80 | this.deleteUser(username); 81 | this.users.set(username, newUser); 82 | } 83 | 84 | async setPassword(username: string, newPassword: string) { 85 | const storedUser: AuthenticationUser = await this.loadUserByUsername(username); 86 | 87 | const newUser: AuthenticationUser = new AuthenticationUserImpl( 88 | username, 89 | newPassword, 90 | storedUser.isEnabled(), 91 | storedUser.getLoginAttemptsLeft(), 92 | storedUser.getPasswordLastChangeDate(), 93 | storedUser.getFirstName(), 94 | storedUser.getLastName(), 95 | storedUser.getAuthorities(), 96 | null, null //when resetting the password, delete the links so they become invalid. 97 | ); 98 | 99 | //delete old user and set a new one, since iface does not support "setPassword()": 100 | this.deleteUser(username); 101 | this.users.set(username, newUser); 102 | } 103 | 104 | async getEncodedPassword(username: string): Promise { 105 | const storedUser: AuthenticationUser = await this.loadUserByUsername(username); 106 | if (!storedUser) 107 | return null; 108 | return Promise.resolve( storedUser.getPassword() ); 109 | } 110 | 111 | async getPasswordLastChangeDate(username: string): Promise { 112 | const storedUser: AuthenticationUser = await this.loadUserByUsername(username); 113 | return Promise.resolve( storedUser.getPasswordLastChangeDate() ); 114 | } 115 | 116 | setAuthorities(username: string, authorities: string[]): any { 117 | throw new Error("Method not implemented."); 118 | } 119 | 120 | async createUser(authenticationUser: AuthenticationUser): Promise { 121 | debug('createUser / inmem implementation!'); 122 | 123 | const newUser: AuthenticationUser = new AuthenticationUserImpl(authenticationUser.getUsername(), 124 | authenticationUser.getPassword(), 125 | false, 126 | authenticationUser.getLoginAttemptsLeft(), 127 | new Date(), 128 | authenticationUser.getFirstName(), 129 | authenticationUser.getLastName(), 130 | authenticationUser.getAuthorities(), 131 | authenticationUser.getToken(), 132 | authenticationUser.getTokenDate()); 133 | 134 | if( await this.userExists( newUser.getUsername() ) ) 135 | { 136 | //ALREADY_EXIST: 137 | throw new Error(`user ${newUser.getUsername()} already exists`); 138 | } 139 | 140 | this.users.set(newUser.getUsername(), newUser); 141 | } 142 | 143 | deleteUser(username: string): void { 144 | this.users.delete(username); 145 | } 146 | 147 | async userExists(username: string): Promise { 148 | debug('userExists?'); 149 | return Promise.resolve( this.users.has(username) ); 150 | } 151 | 152 | async addLink(username: string, link: string) { 153 | const storedUser: AuthenticationUser = await this.loadUserByUsername(username); 154 | 155 | const newUser: AuthenticationUser = new AuthenticationUserImpl( 156 | username, 157 | storedUser.getPassword(), 158 | storedUser.isEnabled(), 159 | storedUser.getLoginAttemptsLeft(), 160 | storedUser.getPasswordLastChangeDate(), 161 | storedUser.getFirstName(), 162 | storedUser.getLastName(), 163 | storedUser.getAuthorities(), 164 | link, 165 | new Date() 166 | ); 167 | 168 | //delete old user and set a new one, since iface does not support "setPassword()": 169 | this.deleteUser(username); 170 | this.users.set(username, newUser); 171 | } 172 | 173 | /** 174 | * remove link 175 | * @param link 176 | */ 177 | async removeLink(username: string): Promise { 178 | const storedUser: AuthenticationUser = await this.loadUserByUsername(username); 179 | 180 | if(!storedUser.getToken()) 181 | return Promise.resolve( false ); 182 | 183 | const newUser: AuthenticationUser = new AuthenticationUserImpl( 184 | username, 185 | storedUser.getPassword(), 186 | storedUser.isEnabled(), 187 | storedUser.getLoginAttemptsLeft(), 188 | storedUser.getPasswordLastChangeDate(), 189 | storedUser.getFirstName(), 190 | storedUser.getLastName(), 191 | storedUser.getAuthorities(), 192 | null 193 | ); 194 | 195 | //delete old user and set a new one, since iface does not support "setPassword()": 196 | this.deleteUser(username); 197 | this.users.set(username, newUser); 198 | return Promise.resolve( true ); 199 | } 200 | //this is for the automation only: 201 | 202 | async getLink(username: string): Promise<{ link: string; date: Date; }> { 203 | const storedUser: AuthenticationUser = await this.loadUserByUsername(username); 204 | return Promise.resolve( { 205 | link: storedUser.getToken(), 206 | date: storedUser.getTokenDate() 207 | } ); 208 | } 209 | 210 | 211 | /** 212 | * in real DB we will index also the link. In-mem impl just iterates over all entries. 213 | * @param link 214 | */ 215 | getUsernameByLink(link: string): Promise { 216 | for (let user of this.users.values()) { 217 | debug(`########### ${user.getToken()} vs ${link}`); 218 | if(user.getToken() === link) 219 | return Promise.resolve( user.getUsername() ); 220 | } 221 | throw new Error("Could not find any user with this link."); 222 | } 223 | 224 | setLastLoginDate(email: string, lastLoginDate: Date) { 225 | } 226 | 227 | getAllUsers(): Promise { 228 | return Promise.resolve(Array.from(this.users.values())); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core/authentication-user-impl' 2 | 3 | export * from './interceptors/create-account-interceptor' 4 | 5 | export * from './types/authentication-flows-error' 6 | export * from './types/authentication-policy' 7 | 8 | export * from './interfaces/authentication-user' 9 | export * from './interfaces/repository/authentication-account-repository' 10 | export * from './interfaces/mail-sender' 11 | 12 | export * from './web/user-action-controller' 13 | 14 | export * from './in-mem-impl/authentication-account-inmem-repository' 15 | -------------------------------------------------------------------------------- /src/interceptors/create-account-interceptor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * this class let applications override this impl and add their custom functionality 3 | */ 4 | export class CreateAccountInterceptor 5 | { 6 | /** 7 | * any other additional validations the app does before account creation. upon failure, exception is thrown. 8 | * @param email 9 | * @param password 10 | * @throws AuthenticationFlowsException 11 | */ 12 | public additionalValidations(email: string, password: string) { 13 | } 14 | 15 | public postCreateAccount( username: string ) { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/interceptors/default-email-sender.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationFlowsError } from ".."; 2 | import * as nodemailer from 'nodemailer'; 3 | import { MailSender } from "../interfaces/mail-sender"; 4 | const debug = require('debug')('authentication-flows:email'); 5 | 6 | export class DefaultMailSenderImpl implements MailSender { 7 | 8 | async sendEmail(recipient: string, 9 | subject: string, 10 | url: string) { 11 | debug('sending email from: ' + process.env.emailSender); 12 | 13 | try { 14 | const transporter = nodemailer.createTransport({ 15 | host: process.env.smtpServer, 16 | port: process.env.smtpPort, 17 | auth: { 18 | user: process.env.emailServerUser, 19 | pass: process.env.emailServerPass 20 | } 21 | }); 22 | 23 | const mailOptions = { 24 | from: process.env.emailSender, 25 | to: recipient, 26 | // from: '"Fred Foo 👻" ', // sender address 27 | // to: "bar@example.com, baz@example.com", // list of receivers 28 | subject, 29 | text: url 30 | }; 31 | 32 | await transporter.sendMail(mailOptions); 33 | debug('email was sent successfully.'); 34 | } catch (me) { 35 | debug(me); 36 | throw new AuthenticationFlowsError(me); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/interfaces/authentication-user.ts: -------------------------------------------------------------------------------- 1 | export interface AuthenticationUser /*extends UserDetails*/ 2 | { 3 | /** 4 | * Returns the authorities granted to the user. 5 | */ 6 | getAuthorities(): string[]; 7 | 8 | /** 9 | * Returns the password used to authenticate the user. 10 | */ 11 | getPassword(): string; 12 | 13 | /** 14 | * Returns the username used to authenticate the user. 15 | */ 16 | getUsername(): string; 17 | 18 | /** 19 | * Indicates whether the user is enabled or disabled. 20 | */ 21 | isEnabled(): boolean; 22 | 23 | /** 24 | * the logic: in the DB we count DOWN the attempts of the user. upon account creation and 25 | * upon login success, we set the "login attempts left" in the DB as set in the props file. 26 | * upon login failure, we decreament the counter. this way, the loginFailureHandler does not 27 | * have to know the "max attempts". only the processor knows this max value. 28 | * @return LoginAttemptsLeft 29 | */ 30 | getLoginAttemptsLeft(): number; 31 | 32 | getPasswordLastChangeDate(): Date; 33 | 34 | getFirstName(): string; 35 | getLastName(): string; 36 | 37 | getToken(): string; 38 | getTokenDate(): Date; 39 | } -------------------------------------------------------------------------------- /src/interfaces/mail-sender.ts: -------------------------------------------------------------------------------- 1 | export interface MailSender { 2 | sendEmail( 3 | recipient: string, 4 | subject: string, 5 | url: string); 6 | } -------------------------------------------------------------------------------- /src/interfaces/password-policy-repository.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationPolicy } from "../types/authentication-policy"; 2 | 3 | export interface PasswordPolicyRepository { 4 | 5 | getDefaultAuthenticationPolicy(): AuthenticationPolicy; 6 | getAuthenticationPolicy(settingsId: number): AuthenticationPolicy; 7 | } 8 | -------------------------------------------------------------------------------- /src/interfaces/repository/authentication-account-repository.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationUser } from "../authentication-user"; 2 | 3 | export interface AuthenticationAccountRepository /*extends UserDetailsManager*/ 4 | { 5 | loadUserByUsername(email: string): Promise; 6 | 7 | /** 8 | * Create a new user with the supplied details. 9 | */ 10 | createUser(authenticationUser: AuthenticationUser): void; 11 | 12 | /** 13 | * Remove the user with the given login name from the system. 14 | * @param email 15 | */ 16 | deleteUser(email: string): void; 17 | 18 | /** 19 | * Check if a user with the supplied login name exists in the system. 20 | */ 21 | userExists(username: string): Promise; 22 | 23 | 24 | setEnabled(email: string); 25 | setDisabled(email: string); 26 | isEnabled(email: string): Promise; 27 | 28 | // boolean changePassword(String username, String newEncodedPassword); 29 | 30 | /** 31 | * 32 | * @param email 33 | */ 34 | decrementAttemptsLeft(email: string); 35 | setAttemptsLeft(email: string, numAttemptsAllowed: number); 36 | 37 | /** 38 | * sets a password for a given user 39 | * @param email - the user's email 40 | * @param newPassword - new password to set 41 | */ 42 | setPassword(email: string, newPassword: string); 43 | 44 | getEncodedPassword(username: string): Promise; 45 | getPasswordLastChangeDate(email: string): Promise; 46 | 47 | setAuthorities(username: string, authorities: string[]); 48 | 49 | // LINKS: 50 | 51 | addLink(username: string, link: string); 52 | 53 | /** 54 | * 55 | * @param username- the key in the map to whom the link is attached 56 | * @return true if link was found (and removed). false otherwise. 57 | */ 58 | removeLink(username: string): Promise; 59 | 60 | getLink(username: string): Promise<{ link: string, date: Date }>; 61 | 62 | /** 63 | * @param link 64 | * @throws Error if link was not found for any user 65 | */ 66 | getUsernameByLink(link: string): Promise; 67 | 68 | setLastLoginDate(email: string, lastLoginDate: Date); 69 | 70 | //------------------- management -------------------// 71 | 72 | getAllUsers(): Promise; 73 | } 74 | -------------------------------------------------------------------------------- /src/types/account-enhancements.ts: -------------------------------------------------------------------------------- 1 | export interface LayerEnhancement { 2 | purchased?: boolean; 3 | datasetName?: string; 4 | } 5 | 6 | export interface AccountEnhancements { 7 | enhancementData: { 8 | [layerId: string]: LayerEnhancement; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/types/authentication-flows-error.ts: -------------------------------------------------------------------------------- 1 | export class AuthenticationFlowsError extends Error { 2 | constructor(message) { 3 | super(message); 4 | this.name = 'MyError'; 5 | } 6 | } 7 | 8 | export class AccountLockedError extends Error { 9 | 10 | } 11 | 12 | export class PasswordAlreadyChangedError extends Error { 13 | 14 | } 15 | 16 | export class LinkExpiredError extends Error { 17 | 18 | } 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/types/authentication-policy.ts: -------------------------------------------------------------------------------- 1 | export class AuthenticationPolicy { 2 | 3 | private readonly passwordMinLength: number; 4 | private readonly passwordMaxLength: number; 5 | private readonly passwordMinUpCaseChars: number; 6 | private readonly passwordMinLoCaseChars: number; 7 | private readonly passwordMinNumbericDigits: number; 8 | private readonly passwordMinSpecialSymbols: number; 9 | private readonly passwordBlackList: string[]; 10 | private readonly maxPasswordEntryAttempts: number; 11 | private readonly passwordLifeInDays: number; 12 | 13 | 14 | public constructor(passwordMinLength: number, 15 | passwordMaxLength: number, 16 | passwordMinUpCaseChars: number, 17 | passwordMinLoCaseChars: number, 18 | passwordMinNumbericDigits: number, 19 | passwordMinSpecialSymbols: number, 20 | passwordBlackList: string[], 21 | maxPasswordEntryAttempts: number, 22 | passwordLifeInDays: number) { 23 | 24 | this.passwordMinLength = passwordMinLength; 25 | this.passwordMaxLength = passwordMaxLength; 26 | this.passwordMinUpCaseChars = passwordMinUpCaseChars; 27 | this.passwordMinLoCaseChars = passwordMinLoCaseChars; 28 | this.passwordMinNumbericDigits = passwordMinNumbericDigits; 29 | this.passwordMinSpecialSymbols = passwordMinSpecialSymbols; 30 | this.maxPasswordEntryAttempts = maxPasswordEntryAttempts; 31 | this.passwordLifeInDays = passwordLifeInDays; 32 | this.passwordBlackList = passwordBlackList; 33 | 34 | } 35 | 36 | public getPasswordMinSpecialSymbols(): number { 37 | return this.passwordMinSpecialSymbols; 38 | } 39 | 40 | public getPasswordBlackList(): string[] { 41 | return this.passwordBlackList; 42 | } 43 | 44 | public getPasswordLifeInDays(): number { 45 | return this.passwordLifeInDays; 46 | } 47 | 48 | public getPasswordMinLength(): number { 49 | return this.passwordMinLength; 50 | } 51 | 52 | public getPasswordMinLoCaseChars(): number { 53 | return this.passwordMinLoCaseChars; 54 | } 55 | 56 | public getPasswordMinUpCaseChars(): number { 57 | return this.passwordMinUpCaseChars; 58 | } 59 | 60 | public getPasswordMaxLength(): number { 61 | return this.passwordMaxLength; 62 | } 63 | 64 | public getPasswordMinNumbericDigits(): number { 65 | return this.passwordMinNumbericDigits; 66 | } 67 | 68 | public getMaxPasswordEntryAttempts(): number { 69 | return this.maxPasswordEntryAttempts; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/types/flows-constatns.ts: -------------------------------------------------------------------------------- 1 | export const OK = "OK"; 2 | export const ERROR = "ERROR"; 3 | 4 | export const HASH_PARAM_NAME = "enc"; 5 | export const REDIRECT_URI_PARAM_NAME = "redirect_uri"; 6 | export const DAY_IN_MILLI = 24 * 60 * 60 * 1000; 7 | export const ETERNAL_PASSWORD = -1; 8 | 9 | export const LOGIN_FORMS_DIR = "login"; 10 | 11 | export const ERR_MSG = "err_msg"; 12 | 13 | 14 | export const ACTIVATE_ACCOUNT_ENDPOINT = '/aa'; 15 | export const RESTORE_PASSWORD_ENDPOINT = '/rp'; 16 | export const CHANGE_PASSWORD_ENDPOINT = '/cp'; 17 | 18 | export const AUTHENTICATION_MAIL_SUBJECT = ": Account Created Successfully"; 19 | export const RESTORE_PASSWORD_MAIL_SUBJECT = ": Password Restore Request"; 20 | export const UNLOCK_MAIL_SUBJECT = ": Account has been Locked"; 21 | export const PASSWORD_CHANGED_MAIL_SUBJECT = ": Password has been Changed"; 22 | 23 | /*export const AUTHENTICATION_MAIL_BODY = "Account Created Successfully. \n\nplease click on this link to activate your account:\n"; 24 | 25 | export const RESTORE_PASSWORD_MAIL_BODY = "you forgot your password and bla bla bla... \n" 26 | + "please click on this link in order to set a new password:\n"; 27 | 28 | export const UNLOCK_MAIL_BODY = "Due to exceeding the allowed number of login attempts, your account has been Locked." 29 | + " \n\nplease click on the link below to unlock your account:\n";*/ 30 | 31 | -------------------------------------------------------------------------------- /src/types/user-info.type.ts: -------------------------------------------------------------------------------- 1 | export interface UserInfo { 2 | account: string, 3 | accountId: string, 4 | accounts: string[], 5 | changePasswordAt: string, 6 | countryCode: string, 7 | createOn: string, 8 | eluaId: string, 9 | email: string, 10 | firstname: string, 11 | isForceChangePassword: boolean, 12 | isForcedToResetPassword: boolean, 13 | lastname: string, 14 | loginAttempts: number, 15 | modifyOn: string, 16 | password: string, 17 | passwords: string[], 18 | role: string, 19 | status: string, 20 | userId: string, 21 | workspaceId: string 22 | } 23 | -------------------------------------------------------------------------------- /src/web/user-action-controller.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationFlowsProcessor } from "../core/authentication-flows-processor"; 2 | import * as url from 'url'; 3 | import * as express from 'express'; 4 | import { 5 | ACTIVATE_ACCOUNT_ENDPOINT, 6 | ERR_MSG, HASH_PARAM_NAME, 7 | RESTORE_PASSWORD_ENDPOINT, 8 | } from "../types/flows-constatns"; 9 | import { 10 | AccountLockedError, 11 | AuthenticationAccountRepository, 12 | AuthenticationUser, CreateAccountInterceptor, 13 | LinkExpiredError, 14 | PasswordAlreadyChangedError 15 | } from ".."; 16 | const debug = require('debug')('authentication-flows:user-action-controller'); 17 | let app; 18 | 19 | /** 20 | * Status code (423) indicating that the resource that is being accessed is locked 21 | */ 22 | const SC_LOCKED = 423; 23 | 24 | export function config(config: { 25 | user_app: object, 26 | authenticationAccountRepository: AuthenticationAccountRepository, 27 | applicationName: string, //the name of the hosting application; will appear in the emails to users 28 | createAccountInterceptor?: CreateAccountInterceptor, 29 | redirectAfterLogin: string, 30 | }) { 31 | app = config.user_app; 32 | AuthenticationFlowsProcessor.instance.authenticationAccountRepository = config.authenticationAccountRepository; 33 | AuthenticationFlowsProcessor.instance.applicationName = config.applicationName; 34 | AuthenticationFlowsProcessor.instance.createAccountInterceptor = config.createAccountInterceptor; 35 | 36 | 37 | 38 | app.get('/login', function(req: express.Request, res: express.Response) { 39 | res.render('login'); 40 | }); 41 | 42 | app.post('/login', async function(req: express.Request, res: express.Response){ 43 | let user: AuthenticationUser; 44 | try { 45 | user = await AuthenticationFlowsProcessor.instance.authenticate( 46 | req.body.username, 47 | req.body.password, 48 | fullUrl(req)); 49 | } 50 | catch(err) { 51 | if (err instanceof AccountLockedError) { 52 | debug(`note: caught AccountLockedError`); 53 | //redirect the user to "account has been locked" page: 54 | res 55 | .status(SC_LOCKED) 56 | .append(ERR_MSG, err.message) //add to headers 57 | .render('accountLockedPage'); 58 | return; 59 | } 60 | 61 | debug(`authentication failed for ${req.body.username}`); 62 | req.session.error = 'Authentication failed, please check your ' 63 | + ' username and password.'; 64 | res 65 | .status(401) 66 | .append(ERR_MSG, 'authentication failed') //add to headers 67 | .render('login'); 68 | return; 69 | } 70 | 71 | debug(user); 72 | 73 | // Regenerate session when signing in 74 | // to prevent fixation 75 | req.session.regenerate(function() { 76 | // Store the user's primary key 77 | // in the session store to be retrieved, 78 | // or in this case the entire user object 79 | req.session.user = user.getUsername(); 80 | req.session.authorities = user.getAuthorities(); 81 | 82 | req.session.success = 'Authenticated as ' + user.getUsername() 83 | + ' click to logout. ' 84 | + ' You may now access /restricted.'; 85 | res.redirect(config.redirectAfterLogin || '/'); 86 | }); 87 | }); 88 | 89 | 90 | app.get('/logout', function(req: express.Request, res: express.Response) { 91 | // destroy the user's session to log them out 92 | // will be re-created next request 93 | req.session.destroy(function() { 94 | res.redirect('/'); 95 | }); 96 | }); 97 | 98 | /** 99 | * The UI calls this method in order to get the password policy 100 | */ 101 | app.get('/getPasswordConstraints', (req, res) => { 102 | debug('getPasswordConstraints'); 103 | res.send('Hello getPasswordConstraints!') 104 | }); 105 | 106 | /** 107 | * The UI calls this method in order to get the password policy 108 | */ 109 | app.get('/createAccount', (req, res) => { 110 | res.render('createAccountPage', { [ERR_MSG]: null }); 111 | }); 112 | 113 | app.post('/createAccount', async (req: express.Request, res: express.Response) => { 114 | const requestBody = req.body; 115 | //debug(`createAccount requestBody ${JSON.stringify(requestBody)}`); 116 | try { 117 | await AuthenticationFlowsProcessor.instance.createAccount( 118 | requestBody.email, 119 | requestBody.password, 120 | requestBody.retypedPassword, 121 | requestBody.firstName, 122 | requestBody.lastName, 123 | fullUrl(req)); 124 | } 125 | catch (e) { 126 | debug('ERROR: ', e); 127 | //back again to createAccountPage, but add error message: 128 | res 129 | .status(500) 130 | .append(ERR_MSG, e.message) //add to headers 131 | .render('createAccountPage', { [ERR_MSG]: e.message }); 132 | return; 133 | } 134 | res.render('accountCreatedSuccess', { email: requestBody.email }); 135 | }); 136 | 137 | app.get(ACTIVATE_ACCOUNT_ENDPOINT + '/:token', async (req: express.Request, res: express.Response) => { 138 | debug('ActivateAccountEndpoint'); 139 | try { 140 | await AuthenticationFlowsProcessor.instance.activateAccount(req.params.token); 141 | } 142 | catch (e) { 143 | debug('ERROR: ', e); 144 | res 145 | .status(500) 146 | .append('err_msg', e.message) 147 | .render('errorPage', { [ERR_MSG]: e.message }); 148 | return; 149 | } 150 | res.render('accountActivated'); 151 | }); 152 | 153 | 154 | /** 155 | * just like createAccount, this controller renders the forgot-password form: 156 | */ 157 | app.get('/forgotPassword', (req: express.Request, res: express.Response) => { 158 | res.render('forgotPasswordPage', { 'err_msg': null }); 159 | }); 160 | 161 | app.post('/forgotPassword', async (req: express.Request, res: express.Response) => { 162 | const requestBody = req.body; 163 | try { 164 | await AuthenticationFlowsProcessor.instance.forgotPassword( 165 | requestBody.email, 166 | fullUrl(req)); 167 | } 168 | catch (e) { 169 | debug('ERROR: ', e); 170 | //back again to forgotPasswordPage, but add error message: 171 | res 172 | .status(500) 173 | .append(ERR_MSG, e.message) //add to headers 174 | .render('forgotPasswordPage', { [ERR_MSG]: e.message }); 175 | return; 176 | } 177 | res 178 | .render('passwordRestoreEmailSent', { email: requestBody.email }); 179 | }); 180 | 181 | app.get(RESTORE_PASSWORD_ENDPOINT + '/:token', async (req: express.Request, res: express.Response) => { 182 | debug('Restore Password Endpoint'); 183 | try { 184 | await AuthenticationFlowsProcessor.instance.validatePasswordRestoration(req.params.token); 185 | } 186 | catch (err) { 187 | debug('ERROR: ', err); 188 | res 189 | .status(500) 190 | .append('err_msg', err.message) //add to headers 191 | .render('errorPage', { [ERR_MSG]: err.message }); 192 | return; 193 | } 194 | // debug('req.params', JSON.stringify(req.params)); 195 | res 196 | .render('setNewPasswordPage', { 197 | [ERR_MSG]: null, 198 | [HASH_PARAM_NAME]: req.params.token 199 | }); 200 | }); 201 | 202 | app.post('/setNewPassword', async (req: express.Request, res: express.Response) => { 203 | 204 | debug('Set New Password Endpoint'); 205 | try { 206 | await AuthenticationFlowsProcessor.instance.setNewPassword( 207 | req.body.enc, 208 | req.body.password, 209 | req.body.retypedPassword); 210 | } 211 | catch (e) { 212 | debug('ERROR: ', e); 213 | //back again to setNewPasswordPage, but add error message: 214 | res 215 | .status(500) 216 | .append(ERR_MSG, e.message) //add to headers 217 | .render('setNewPasswordPage', { 218 | [ERR_MSG]: e.message, 219 | [HASH_PARAM_NAME]: req.body.enc 220 | }); 221 | return; 222 | } 223 | res 224 | .render('passwordSetSuccess'); 225 | }); 226 | 227 | 228 | app.get('/changePassword', (req: express.Request, res: express.Response) => { 229 | res.render('changePasswordPage', { [ERR_MSG]: null }); 230 | }); 231 | 232 | app.post('/changePassword', async (req: express.Request, res: express.Response) => { 233 | const requestBody = req.body; 234 | debug('req.session.user: ', req.session.user); 235 | 236 | try { 237 | await AuthenticationFlowsProcessor.instance.changePassword( 238 | req.session.user, 239 | requestBody.currentPassword, 240 | requestBody.newPassword, 241 | requestBody.retypedPassword); 242 | } 243 | catch (e) { 244 | debug('ERROR: ', e); 245 | //back again to changePasswordPage, but add error message: 246 | res 247 | .status(500) 248 | .append(ERR_MSG, e.message) //add to headers 249 | .render('changePasswordPage', { [ERR_MSG]: e.message }); 250 | return; 251 | } 252 | res 253 | .render('passwordChangedEmailSent', { email: requestBody.email }); 254 | }); 255 | 256 | app.post('/deleteAccount', async (req: express.Request, res: express.Response) => { 257 | const requestBody = req.body; 258 | //debug(`createAccount requestBody ${JSON.stringify(requestBody)}`); 259 | try { 260 | await AuthenticationFlowsProcessor.instance.deleteAccount( 261 | requestBody.email, 262 | requestBody.password); 263 | } 264 | catch (e) { 265 | debug('ERROR: ', e); 266 | //back again to deleteAccountPage, but add error message: 267 | res 268 | .status(500) 269 | .render('deleteAccountPage', { [ERR_MSG]: e.message }); 270 | return; 271 | } 272 | res 273 | .render('accountDeletedSuccess', { email: requestBody.email }); 274 | }); 275 | 276 | //------------------- management -------------------// 277 | 278 | app.get('/user', async (req: express.Request, res: express.Response) => { 279 | let users: AuthenticationUser[]; 280 | try { 281 | users = await AuthenticationFlowsProcessor.instance.getAllUsers(); 282 | } catch (err) { 283 | debug('ERROR: ', err); 284 | res 285 | .status(500) 286 | .append('err_msg', err.message) //add to headers 287 | .json({ [ERR_MSG]: err.message }); 288 | return; 289 | } 290 | // debug('req.params', JSON.stringify(req.params)); 291 | res.json(users); 292 | }); 293 | 294 | app.put('/user/:email/authorities', async (req: express.Request, res: express.Response) => { 295 | let result: string; 296 | try { 297 | result = await AuthenticationFlowsProcessor.instance.setAuthoritiesForUser(req.params.email, req.body.authorities); 298 | } catch (err) { 299 | debug('ERROR: ', err); 300 | res 301 | .status(500) 302 | .append('err_msg', err.message) //add to headers 303 | .json({ [ERR_MSG]: err.message }); 304 | return; 305 | } 306 | // debug('req.params', JSON.stringify(req.params)); 307 | res.json(result); 308 | }); 309 | } 310 | 311 | export function fullUrl(req: express.Request): string { 312 | return url.format({ 313 | protocol: req.protocol, 314 | host: req.get('host'), //host + port 315 | // pathname: req.originalUrl 316 | pathname: req.baseUrl 317 | }); 318 | } 319 | 320 | -------------------------------------------------------------------------------- /test/crypto/crpyto.test.ts: -------------------------------------------------------------------------------- 1 | // Defining a text to be encrypted 2 | import { 3 | encryptString, 4 | decryptString, 5 | generateKeyFile, shaString 6 | } from "../../src"; 7 | import * as crypto from 'crypto'; 8 | 9 | //generate the key file: 10 | generateKeyFile(); 11 | 12 | const plainText = "ohad redlich is the man"; 13 | 14 | // Prints plain text 15 | console.log("Plaintext: ", plainText); 16 | 17 | // Defining encrypted text 18 | const encrypted = encryptString(plainText); 19 | 20 | // Prints encrypted text 21 | console.log("Encrypted: ", encrypted); 22 | 23 | const decrypted = decryptString(encrypted); 24 | console.log("Decrypted: ", decrypted); 25 | 26 | const a = encryptString('plainText'); 27 | const b = encryptString('plainText'); 28 | const c = encryptString('plainText'); 29 | console.log(`pass encrypted 1: ${ a }`); 30 | console.log(`pass encrypted 2: ${ b }`); 31 | console.log(`pass encrypted 2: ${ c }`); 32 | console.log(`decrypted 1: ${ decryptString(a) }`); 33 | console.log(`decrypted 2: ${ decryptString(b) }`); 34 | console.log(`decrypted 3: ${ decryptString(c) }`); 35 | 36 | 37 | //testSymetryBase64(); 38 | function testSymetryBase64() { 39 | const b1 = Buffer.from('plainText').toString('base64'); //default is utf8 40 | const b2 = Buffer.from('plainText', 'utf8').toString('base64'); 41 | const b3 = Buffer.from('plainText', 'utf8').toString('base64'); 42 | console.log(`b1: ${b1}`); 43 | console.log(`b2: ${b2}`); 44 | console.log(`b3: ${b3}`); 45 | console.log(`decrypted 1: ${Buffer.from(b1, 'base64')}`); 46 | } 47 | 48 | testSHA256(); 49 | function testSHA256() { 50 | const hashed = shaString('ohad redlich is the man of crypto!!'); 51 | const hashed2 = shaString('ohad redlich is the man of crypto!!'); 52 | console.log(`hashed1: ${hashed}`); 53 | console.log(`hashed2: ${hashed2}`); 54 | } -------------------------------------------------------------------------------- /test/mail/mail.test.ts: -------------------------------------------------------------------------------- 1 | import { DefaultMailSenderImpl } from "../../src/interceptors/default-email-sender"; 2 | 3 | new DefaultMailSenderImpl().sendEmail('ohad.redlich@intel.com', 'hello me!', 'url'); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "types": ["node"], 6 | "experimentalDecorators": true, 7 | "emitDecoratorMetadata": true, 8 | "declaration": true, 9 | "noImplicitAny": false, 10 | "sourceMap": true, 11 | "outDir": "dist" 12 | }, 13 | "include": [ 14 | "src/**/*.ts", 15 | "tests/**/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | --------------------------------------------------------------------------------