├── .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 | #
authentication-flows-js
2 |
3 | [](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 | [](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 | 
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 | 
142 |
143 | ### Forgot Password
144 |
145 | 
146 |
147 | ### Change Password
148 |
149 | 
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 |
--------------------------------------------------------------------------------