commit 0f726741f9b008a9a8c7547b942d92739b8f776e Author: Evert Prants Date: Fri Feb 25 18:31:59 2022 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..8eba6c8 --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +src/ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..259b1cb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1178 @@ +{ + "name": "@icynet/oauth2", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@icynet/oauth2", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "express": "^4.17.3", + "express-session": "^1.17.2" + }, + "devDependencies": { + "@types/express": "^4.17.13", + "@types/express-session": "^1.17.4", + "@types/node": "^17.0.21", + "typescript": "^4.5.5" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/express-session": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", + "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dependencies": { + "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + } + }, + "dependencies": { + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/express-session": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", + "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "requires": { + "mime-db": "1.51.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "requires": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f4c18ca --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "@icynet/oauth2-provider", + "version": "1.0.0", + "description": "OAuth2.0 Provider for Icy Network", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "prepublish": "npm run build", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Evert ", + "license": "MIT", + "devDependencies": { + "@types/express": "^4.17.13", + "@types/express-session": "^1.17.4", + "@types/node": "^17.0.21", + "typescript": "^4.5.5" + }, + "dependencies": { + "express": "^4.17.3", + "express-session": "^1.17.2" + } +} diff --git a/src/controller/authorization.ts b/src/controller/authorization.ts new file mode 100644 index 0000000..cbe25bb --- /dev/null +++ b/src/controller/authorization.ts @@ -0,0 +1,196 @@ +import { + InvalidRequest, + UnsupportedResponseType, + InvalidClient, + UnauthorizedClient, + InvalidScope, + AccessDenied, +} from '../model/error'; +import { OAuth2User } from '../model/model'; +import { data as dataResponse } from '../utils/response'; +import wrap from '../utils/wrap'; + +/** + * Authorization and decision endpoint. + */ +export const authorization = wrap(async (req, res) => { + let clientId: string | null = null; + let redirectUri: string | null = null; + let responseType: string | null = null; + let grantTypes: string[] = []; + let scope: string[] | null = null; + let user: OAuth2User | null = null; + + const { oauth2 } = req; + + if (!req.query.redirect_uri) { + throw new InvalidRequest('redirect_uri field is mandatory for authorization endpoint'); + } + + redirectUri = req.query.redirect_uri as string; + console.debug('Parameter redirect uri is', redirectUri); + + if (!req.query.client_id) { + throw new InvalidRequest('client_id field is mandatory for authorization endpoint'); + } + + // Check for client_secret (prevent passing it) + if (req.query.client_secret) { + throw new InvalidRequest('client_secret field should not be passed to the authorization endpoint'); + } + + clientId = req.query.client_id as string; + console.debug('Parameter client_id is', clientId); + + if (!req.query.response_type) { + throw new InvalidRequest('response_type field is mandatory for authorization endpoint'); + } + + responseType = req.query.response_type as string; + console.debug('Parameter response_type is', responseType); + + // Support multiple types + const responseTypes = responseType.split(' '); + for (const i in responseTypes) { + switch (responseTypes[i]) { + case 'code': + grantTypes.push('authorization_code'); + break; + case 'token': + grantTypes.push('implicit'); + break; + // case 'id_token': + case 'none': + grantTypes.push(responseTypes[i]); + break; + default: + throw new UnsupportedResponseType('Unknown response_type parameter passed'); + } + } + + // Filter out duplicates + grantTypes = grantTypes.filter((value, index, self) => self.indexOf(value) === index); + + // "None" type cannot be combined with others + if (grantTypes.length > 1 && grantTypes.indexOf('none') !== -1) { + throw new InvalidRequest('Grant type "none" cannot be combined with other grant types'); + } + + console.debug('Parameter grant_type is', grantTypes.join(' ')); + + const client = await oauth2.model.client.fetchById(clientId); + if (!client) { + throw new InvalidClient('Client not found'); + } + + // TODO: multiple redirect URI + if (!oauth2.model.client.getRedirectUri(client)) { + throw new UnsupportedResponseType('The client has not set a redirect uri'); + } else if (!oauth2.model.client.checkRedirectUri(client, redirectUri)) { + throw new InvalidRequest('Wrong RedirectUri provided'); + } + console.debug('redirect_uri check passed'); + + // The client needs to support all grant types + for (const grantType of grantTypes) { + if (!oauth2.model.client.checkGrantType(client, grantType) && grantType !== 'none') { + throw new UnauthorizedClient('This client does not support grant type ' + grantType); + } + } + console.debug('Grant type check passed'); + + scope = oauth2.model.client.transformScope(req.query.scope as string); + if (!oauth2.model.client.checkScope(client, scope)) { + throw new InvalidScope('Client does not allow access to this scope'); + } + console.debug('Scope check passed'); + + user = await oauth2.model.user.fetchFromRequest(req); + if (!user) { + throw new InvalidRequest('There is no currently logged in user'); + } else { + if (!user.username) { + throw new AccessDenied(user.username); + } + console.debug('User fetched from request') + } + + let resObj = {}; + let consented = false; + + if (req.method === 'GET') { + // Check if the user has already consented to this client with this scope + // TODO: reevaluate security implications + consented = await oauth2.model.user.consented( + oauth2.model.user.getId(user), + oauth2.model.client.getId(client), + scope + ); + + // Ask for consent + if (!consented) return oauth2.decision(req, res, client, scope, user, redirectUri); + } + + // Consent pushed, ensure valid session + const { session: { csrf } } = req; + if (req.method === 'POST' && csrf && !(req.body.csrf && req.body.csrf === csrf)) { + throw new InvalidRequest('Invalid session'); + } + + // Save consent + if (!consented) { + if (!req.body || (typeof req.body.decision) === 'undefined') { + throw new InvalidRequest('No decision parameter passed'); + } else if (req.body.decision === '0') { + throw new AccessDenied('User denied access to the resource'); + } + + console.debug('Decision check passed'); + + await oauth2.model.user.consent( + oauth2.model.user.getId(user), + oauth2.model.client.getId(client), + scope + ); + } + + for (const i in grantTypes) { + let data = null + switch (grantTypes[i]) { + case 'authorization_code': + data = await oauth2.model.code.create( + oauth2.model.user.getId(user), + oauth2.model.client.getId(client), + scope, + oauth2.model.code.ttl, + ); + + resObj = Object.assign({ code: data }, resObj); + + break; + case 'implicit': + data = await oauth2.model.accessToken.create( + oauth2.model.user.getId(user), + oauth2.model.client.getId(client), + scope, + oauth2.model.accessToken.ttl + ); + + resObj = Object.assign({ + token_type: 'bearer', + access_token: data, + expires_in: oauth2.model.accessToken.ttl + }, resObj); + + break; + case 'none': + resObj = {}; + break; + default: + throw new UnsupportedResponseType('Unknown response_type parameter passed'); + } + } + + // Return non-code response types as fragment instead of query + return dataResponse(req, res, resObj, redirectUri, responseType !== 'code'); +}, true); diff --git a/src/controller/index.ts b/src/controller/index.ts new file mode 100644 index 0000000..4a69554 --- /dev/null +++ b/src/controller/index.ts @@ -0,0 +1,3 @@ +export * from './authorization'; +export * from './introspection'; +export * from './token'; diff --git a/src/controller/introspection.ts b/src/controller/introspection.ts new file mode 100644 index 0000000..c9da32a --- /dev/null +++ b/src/controller/introspection.ts @@ -0,0 +1,56 @@ +import { InvalidRequest } from '../model/error'; +import { data as dataResponse } from '../utils/response'; +import wrap from '../utils/wrap'; + +export const introspection = wrap(async function (req, res) { + let clientId: string | null = null; + let clientSecret: string | null = null; + + const { oauth2 } = req; + + if (req.body.client_id && req.body.client_secret) { + clientId = req.body.client_id as string; + clientSecret = req.body.client_secret as string; + console.debug('Client credentials parsed from body parameters ', clientId, clientSecret); + } else { + if (!req.headers || !req.headers.authorization) { + throw new InvalidRequest('No authorization header passed'); + } + + let pieces = req.headers.authorization.split(' ', 2); + if (!pieces || pieces.length !== 2) { + throw new InvalidRequest('Authorization header is corrupted'); + } + + if (pieces[0] !== 'Basic') { + throw new InvalidRequest(`Unsupported authorization method: ${pieces[0]}`); + } + + pieces = Buffer.from(pieces[1], 'base64').toString('ascii').split(':', 2); + if (!pieces || pieces.length !== 2) { + throw new InvalidRequest('Authorization header has corrupted data'); + } + + clientId = pieces[0]; + clientSecret = pieces[1]; + console.debug('Client credentials parsed from basic auth header: ', clientId, clientSecret); + } + + if (!req.body.token) { + throw new InvalidRequest('Token not provided in request body'); + } + + const token = await oauth2.model.accessToken.fetchByToken(req.body.token); + if (!token) { + throw new InvalidRequest('Token does not exist'); + } + + const ttl = oauth2.model.accessToken.getTTL(token); + const resObj = { + token_type: 'bearer', + token: token.token, + expires_in: Math.floor(ttl / 1000) + }; + + dataResponse(req, res, resObj); +}); diff --git a/src/controller/token.ts b/src/controller/token.ts new file mode 100644 index 0000000..847e011 --- /dev/null +++ b/src/controller/token.ts @@ -0,0 +1,98 @@ +import * as tokens from './tokens' +import { + InvalidRequest, + InvalidClient, + UnauthorizedClient, + UnsupportedGrantType, + OAuth2Error +} from '../model/error' +import { data as dataResponse, error as errorResponse } from '../utils/response' +import wrap from '../utils/wrap' +import { OAuth2TokenResponse } from '../model/model'; + +export const token = wrap(async (req, res) => { + let clientId: string | null = null; + let clientSecret: string | null = null; + let grantType: string | null = null; + + const { oauth2 } = req; + + if (req.body.client_id && req.body.client_secret) { + clientId = req.body.client_id as string; + clientSecret = req.body.client_secret as string; + console.debug('Client credentials parsed from body parameters', clientId, clientSecret); + } else { + if (!req.headers || !req.headers.authorization) { + throw new InvalidRequest('No authorization header passed'); + } + + let pieces = req.headers.authorization.split(' ', 2); + if (!pieces || pieces.length !== 2) { + throw new InvalidRequest('Authorization header is corrupted'); + } + + if (pieces[0] !== 'Basic') { + throw new InvalidRequest(`Unsupported authorization method: ${pieces[0]}`); + } + + pieces = Buffer.from(pieces[1], 'base64').toString('ascii').split(':', 2); + if (!pieces || pieces.length !== 2) { + throw new InvalidRequest('Authorization header has corrupted data'); + } + + clientId = pieces[0]; + clientSecret = pieces[1]; + console.debug('Client credentials parsed from basic auth header:', clientId, clientSecret); + } + + if (!req.body.grant_type) { + throw new InvalidRequest('Request body does not contain grant_type parameter'); + } + + grantType = req.body.grant_type as string; + console.debug('Parameter grant_type is', grantType); + + const client = await oauth2.model.client.fetchById(clientId); + + if (!client) { + throw new InvalidClient('Client not found'); + } + + const valid = oauth2.model.client.checkSecret(client, clientSecret); + if (!valid) { + throw new UnauthorizedClient('Invalid client secret'); + } + + if (!oauth2.model.client.checkGrantType(client, grantType) && grantType !== 'refresh_token') { + throw new UnauthorizedClient('Invalid grant type for the client'); + } else { + console.debug('Grant type check passed'); + } + + let tokenResponse: OAuth2TokenResponse; + try { + switch (grantType) { + case 'authorization_code': + tokenResponse = await tokens.authorizationCode(oauth2, client, req.body.code); + break; + case 'password': + tokenResponse = await tokens.password(oauth2, client, req.body.username, req.body.password, req.body.scope); + break; + case 'client_credentials': + tokenResponse = await tokens.clientCredentials(oauth2, client, req.body.scope); + break; + case 'refresh_token': + tokenResponse = await tokens.refreshToken(oauth2, client, req.body.refresh_token); + break; + default: + throw new UnsupportedGrantType('Grant type does not match any supported type'); + } + + if (tokenResponse) { + dataResponse(req, res, tokenResponse); + } + + } catch (e) { + errorResponse(req, res, e as OAuth2Error); + } +}) diff --git a/src/controller/tokens/authorizationCode.ts b/src/controller/tokens/authorizationCode.ts new file mode 100644 index 0000000..ba2ab71 --- /dev/null +++ b/src/controller/tokens/authorizationCode.ts @@ -0,0 +1,97 @@ +import { InvalidRequest, ServerError, InvalidGrant } from '../../model/error'; +import { OAuth2, OAuth2Client, OAuth2Code, OAuth2TokenResponse } from '../../model/model'; + +/** + * Issue an access token by authorization code + * @param oauth2 - OAuth2 instance + * @param client - OAuth2 client + * @param providedCode - Authorization code + * @returns Access token. + */ +export async function authorizationCode( + oauth2: OAuth2, + client: OAuth2Client, + providedCode: string +): Promise { + const respObj: OAuth2TokenResponse = { + token_type: 'bearer' + }; + + let code: OAuth2Code | null = null; + + if (!providedCode) { + throw new InvalidRequest('code is mandatory for authorization_code grant type'); + } + + try { + code = await oauth2.model.code.fetchByCode(providedCode); + } catch (err) { + console.error(err); + throw new ServerError('Failed to call code.fetchByCode function'); + } + + if (code) { + if (oauth2.model.code.getClientId(code) !== oauth2.model.client.getId(client)) { + throw new InvalidGrant('Code was issued by another client'); + } + + if (!oauth2.model.code.checkTTL(code)) { + throw new InvalidGrant('Code has already expired'); + } + } else { + throw new InvalidGrant('Code not found'); + } + + console.debug('Code fetched', code); + + try { + await oauth2.model.refreshToken.removeByUserIdClientId( + oauth2.model.code.getUserId(code), + oauth2.model.code.getClientId(code) + ); + } catch (err) { + console.error(err) + throw new ServerError('Failed to call refreshToken.removeByUserIdClientId function'); + } + + console.debug('Refresh token removed'); + + if (!oauth2.model.client.checkGrantType(client, 'refresh_token')) { + console.debug('Client does not allow grant type refresh_token, skip creation'); + } else { + try { + respObj.refresh_token = await oauth2.model.refreshToken.create( + oauth2.model.code.getUserId(code), + oauth2.model.code.getClientId(code), + oauth2.model.code.getScope(code) + ); + } catch (err) { + console.error(err); + throw new ServerError('Failed to call refreshToken.create function'); + } + } + + try { + respObj.access_token = await oauth2.model.accessToken.create( + oauth2.model.code.getUserId(code), + oauth2.model.code.getClientId(code), + oauth2.model.code.getScope(code), + oauth2.model.accessToken.ttl + ); + } catch (err) { + console.error(err); + throw new ServerError('Failed to call accessToken.create function'); + } + + respObj.expires_in = oauth2.model.accessToken.ttl; + console.debug('Access token saved:', respObj.access_token); + + try { + await oauth2.model.code.removeByCode(providedCode); + } catch (err) { + console.error(err); + throw new ServerError('Failed to call code.removeByCode function'); + } + + return respObj; +} diff --git a/src/controller/tokens/clientCredentials.ts b/src/controller/tokens/clientCredentials.ts new file mode 100644 index 0000000..e2a0035 --- /dev/null +++ b/src/controller/tokens/clientCredentials.ts @@ -0,0 +1,43 @@ +import { ServerError, InvalidScope } from '../../model/error' +import { OAuth2, OAuth2Client, OAuth2TokenResponse } from '../../model/model'; + +/** + * Issue client access token + * @param oauth2 - OAuth2 instance + * @param client - Client + * @param wantScope - Requested scopes + * @returns Access token + */ +export async function clientCredentials( + oauth2: OAuth2, + client: OAuth2Client, + wantScope: string | string[] +): Promise { + let scope: string[] = []; + + const resObj: OAuth2TokenResponse = { + token_type: 'bearer' + }; + + scope = oauth2.model.client.transformScope(wantScope); + if (!oauth2.model.client.checkScope(client, scope)) { + throw new InvalidScope('Client does not allow access to this scope'); + } + + console.debug('Scope check passed ', scope); + + try { + resObj.access_token = await oauth2.model.accessToken.create( + null, + oauth2.model.client.getId(client), + scope, + oauth2.model.accessToken.ttl + ); + } catch (err) { + throw new ServerError('Failed to call accessToken.create function'); + } + + resObj.expires_in = oauth2.model.accessToken.ttl; + + return resObj; +} diff --git a/src/controller/tokens/index.ts b/src/controller/tokens/index.ts new file mode 100644 index 0000000..24e814c --- /dev/null +++ b/src/controller/tokens/index.ts @@ -0,0 +1,4 @@ +export * from './authorizationCode' +export * from './clientCredentials' +export * from './password' +export * from './refreshToken' diff --git a/src/controller/tokens/password.ts b/src/controller/tokens/password.ts new file mode 100644 index 0000000..0e68449 --- /dev/null +++ b/src/controller/tokens/password.ts @@ -0,0 +1,96 @@ +import { ServerError, InvalidRequest, InvalidScope, InvalidClient } from '../../model/error' +import { OAuth2, OAuth2Client, OAuth2User, OAuth2TokenResponse } from '../../model/model'; + +/** + * Implicit access token response + * @param oauth2 - OAuth2 instance + * @param client - OAuth2 client + * @param username + * @param password + * @param scope - Requested scopes + * @returns Access token + */ +export async function password( + oauth2: OAuth2, + client: OAuth2Client, + username: string, + password: string, + scope: string | string[] +): Promise { + let user: OAuth2User | null = null; + + const resObj: OAuth2TokenResponse = { + token_type: 'bearer' + } + + if (!username) { + throw new InvalidRequest('Username is mandatory for password grant type'); + } + + if (!password) { + throw new InvalidRequest('Password is mandatory for password grant type'); + } + + scope = oauth2.model.client.transformScope(scope); + if (!oauth2.model.client.checkScope(client, scope)) { + throw new InvalidScope('Client does not allow access to this scope'); + } else { + console.debug('Scope check passed: ', scope); + } + + try { + user = await oauth2.model.user.fetchByUsername(username); + } catch (err) { + throw new ServerError('Failed to call user.fetchByUsername function'); + } + + if (!user) { + throw new InvalidClient('User not found'); + } + + const valid = await oauth2.model.user.checkPassword(user, password); + if (!valid) { + throw new InvalidClient('Wrong password'); + } + + try { + await oauth2.model.refreshToken.removeByUserIdClientId( + oauth2.model.user.getId(user), + oauth2.model.client.getId(client) + ); + } catch (err) { + throw new ServerError('Failed to call refreshToken.removeByUserIdClientId function'); + } + + console.debug('Refresh token removed'); + + if (!oauth2.model.client.checkGrantType(client, 'refresh_token')) { + console.debug('Client does not allow grant type refresh_token, skip creation'); + } else { + try { + resObj.refresh_token = await oauth2.model.refreshToken.create( + oauth2.model.user.getId(user), + oauth2.model.client.getId(client), + scope + ); + } catch (err) { + throw new ServerError('Failed to call refreshToken.create function'); + } + } + + try { + resObj.access_token = await oauth2.model.accessToken.create( + oauth2.model.user.getId(user), + oauth2.model.client.getId(client), + scope, + oauth2.model.accessToken.ttl + ); + } catch (err) { + throw new ServerError('Failed to call accessToken.create function'); + } + + resObj.expires_in = oauth2.model.accessToken.ttl; + console.debug('Access token saved ', resObj.access_token); + + return resObj; +} diff --git a/src/controller/tokens/refreshToken.ts b/src/controller/tokens/refreshToken.ts new file mode 100644 index 0000000..90d94a8 --- /dev/null +++ b/src/controller/tokens/refreshToken.ts @@ -0,0 +1,98 @@ +import { InvalidRequest, ServerError, InvalidGrant, InvalidClient } from '../../model/error'; +import { + OAuth2, + OAuth2AccessToken, + OAuth2Client, + OAuth2RefreshToken, + OAuth2User, + OAuth2TokenResponse +} from '../../model/model'; + +/** + * Get a new access token from a refresh token. Scope change may not be requested. + * @param oauth2 - OAuth2 instance + * @param client - OAuth2 client + * @param pRefreshToken - Refresh token + * @returns Access token + */ +export async function refreshToken( + oauth2: OAuth2, + client: OAuth2Client, + pRefreshToken: string, +): Promise { + let user: OAuth2User | null = null; + let ttl: number | null = null; + let refreshToken: OAuth2RefreshToken | null = null; + let accessToken: OAuth2AccessToken | null = null; + + const resObj: OAuth2TokenResponse = { + token_type: 'bearer' + }; + + if (!pRefreshToken) { + throw new InvalidRequest('refresh_token is mandatory for refresh_token grant type'); + } + + try { + refreshToken = await oauth2.model.refreshToken.fetchByToken(pRefreshToken); + } catch (err) { + throw new ServerError('Failed to call refreshToken.fetchByToken function'); + } + + if (!refreshToken) { + throw new InvalidGrant('Refresh token not found'); + } + + if (oauth2.model.refreshToken.getClientId(refreshToken) !== oauth2.model.client.getId(client)) { + console.warn('Client %s tried to fetch a refresh token which belongs to client %s!', + oauth2.model.client.getId(client), + oauth2.model.refreshToken.getClientId(refreshToken) + ); + throw new InvalidGrant('Refresh token not found'); + } + + try { + user = await oauth2.model.user.fetchById(oauth2.model.refreshToken.getUserId(refreshToken)); + } catch (err) { + throw new InvalidClient('User not found'); + } + + if (!user) { + throw new InvalidClient('User not found'); + } + + try { + accessToken = await oauth2.model.accessToken.fetchByUserIdClientId(oauth2.model.user.getId(user), + oauth2.model.client.getId(client)); + } catch (err) { + throw new ServerError('Failed to call accessToken.fetchByUserIdClientId function'); + } + + if (accessToken) { + ttl = oauth2.model.accessToken.getTTL(accessToken); + + if (!ttl) { + accessToken = null; + } else { + resObj.access_token = oauth2.model.accessToken.getToken(accessToken); + resObj.expires_in = ttl; + } + } + + if (!accessToken) { + try { + resObj.access_token = await oauth2.model.accessToken.create( + oauth2.model.user.getId(user), + oauth2.model.client.getId(client), + oauth2.model.refreshToken.getScope(refreshToken), + oauth2.model.accessToken.ttl + ); + } catch (err) { + throw new ServerError('Failed to call accessToken.create function'); + } + + resObj.expires_in = oauth2.model.accessToken.ttl; + } + + return resObj; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e165f86 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,7 @@ +export * from './model/model'; + +export * from './model/error'; +export * from './controller'; + +export * from './middleware'; +export * from './provider'; diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..6c86e87 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,46 @@ +import { Request } from 'express'; +import { AccessDenied } from './model/error'; +import wrap from './utils/wrap'; + +export const middleware = wrap(async function (req: Request, res, next) { + console.debug('Parsing bearer token'); + let token = null; + + // Look for token in header + if (req.headers.authorization) { + const pieces = req.headers.authorization.split(' ', 2); + + // Check authorization header + if (!pieces || pieces.length !== 2) { + throw new AccessDenied('Wrong authorization header'); + } + + // Only bearer auth is supported + if (pieces[0].toLowerCase() !== 'bearer') { + throw new AccessDenied('Unsupported authorization method in header'); + } + + token = pieces[1]; + console.debug('Bearer token parsed from authorization header:', token); + } else if (req.query?.access_token) { + token = req.query.access_token; + console.debug('Bearer token parsed from query params:', token); + } else if (req.body?.access_token) { + token = req.body.access_token; + console.debug('Bearer token parsed from body params:', token); + } else { + throw new AccessDenied('Bearer token not found'); + } + + // Try to fetch access token + const object = await req.oauth2.model.accessToken.fetchByToken(token); + if (!object) { + throw new AccessDenied('Token not found or has expired'); + } else if (!req.oauth2.model.accessToken.checkTTL(object)) { + throw new AccessDenied('Token is expired'); + } else { + res.locals.accessToken = object; + console.debug('AccessToken fetched', object); + next(); + } +}); diff --git a/src/model/error.ts b/src/model/error.ts new file mode 100644 index 0000000..0e2cb0e --- /dev/null +++ b/src/model/error.ts @@ -0,0 +1,90 @@ +export class OAuth2Error extends Error { + public name = 'OAuth2AbstractError'; + public logLevel = 'error'; + + constructor (public code: string, public message: string, public status: number) { + super() + Error.captureStackTrace(this, this.constructor); + } +} + +export class AccessDenied extends OAuth2Error { + public name = 'OAuth2AccessDenied'; + public logLevel = 'info'; + + constructor (msg: string) { + super('access_denied', msg, 403); + } +} + +export class InvalidClient extends OAuth2Error { + public name = 'OAuth2InvalidClient'; + public logLevel = 'info'; + + constructor (msg: string) { + super('invalid_client', msg, 401); + } +} + +export class InvalidGrant extends OAuth2Error { + public name = 'OAuth2InvalidGrant'; + public logLevel = 'info'; + + constructor (msg: string) { + super('invalid_grant', msg, 400); + } +} + +export class InvalidRequest extends OAuth2Error { + public name = 'OAuth2InvalidRequest'; + public logLevel = 'info'; + + constructor (msg: string) { + super('invalid_request', msg, 400); + } +} + +export class InvalidScope extends OAuth2Error { + public name = 'OAuth2InvalidScope'; + public logLevel = 'info'; + + constructor (msg: string) { + super('invalid_scope', msg, 400); + } +} + +export class ServerError extends OAuth2Error { + public name = 'OAuth2ServerError'; + public logLevel = 'error'; + + constructor (msg: string) { + super('server_error', msg, 500); + } +} + +export class UnauthorizedClient extends OAuth2Error { + public name = 'OAuth2UnauthorizedClient'; + public logLevel = 'info'; + + constructor (msg: string) { + super('unauthorized_client', msg, 400); + } +} + +export class UnsupportedGrantType extends OAuth2Error { + public name = 'OAuth2UnsupportedGrantType'; + public logLevel = 'info'; + + constructor (msg: string) { + super('unsupported_grant_type', msg, 400); + } +} + +export class UnsupportedResponseType extends OAuth2Error { + public name = 'OAuth2UnsupportedResponseType'; + public logLevel = 'info'; + + constructor (msg: string) { + super('unsupported_response_type', msg, 400); + } +} diff --git a/src/model/index.ts b/src/model/index.ts new file mode 100644 index 0000000..b7c1940 --- /dev/null +++ b/src/model/index.ts @@ -0,0 +1,2 @@ +export * from './error'; +export * from './model'; diff --git a/src/model/model.ts b/src/model/model.ts new file mode 100644 index 0000000..6c9a391 --- /dev/null +++ b/src/model/model.ts @@ -0,0 +1,332 @@ +import { Request, Response } from 'express'; + +/** + * OAuth2 client object + */ +export interface OAuth2Client { + id: string | number; + secret: string; + scope: string[]; + grants: string[]; +} + +/** + * OAuth2 access token object + */ +export interface OAuth2AccessToken { + token: string; + user_id: string | number; + client_id: string | number; + scope: string; + expires_at: number; +} + +/** + * OAuth2 authorization code object + */ +export interface OAuth2Code { + code: string; + expires_at: number; + user_id: string | number; + client_id: string | number; + scope: string; +} + +/** + * OAuth2 refresh token object + */ +export interface OAuth2RefreshToken { + token: string; + user_id: string | number; + client_id: string | number; + scope: string; +} + +/** + * OAuth2 implicit user model + */ +export interface OAuth2User { + id: string | number; + username: string; + password: string; +} + +/** + * OAuth2 token response + */ +export interface OAuth2TokenResponse { + access_token?: string; + refresh_token?: string; + expires_in?: number; + token_type?: string; + state?: string; +} + +/** + * OAuth2 access token adapter model + */ +export interface OAuth2AccessTokenAdapter { + /** + * Static time-to-live in seconds for access token + */ + ttl: number; + + /** + * Get the token string from an access token object + */ + getToken: (token: OAuth2AccessToken) => string; + + /** + * Create a new access token + */ + create: ( + userId: string | number | null, + clientId: string | number, + scope: string | string[], + ttl: number + ) => Promise; + + /** + * Fetch an access token by the token string from the database + */ + fetchByToken: (token: OAuth2AccessToken | string) => Promise; + + /** + * Check the time-to-live value + */ + checkTTL: (token: OAuth2AccessToken) => boolean; + + /** + * Get the time-to-live value from a token object + */ + getTTL: (token: OAuth2AccessToken) => number; + + /** + * Fetch an access token by user ID and client ID from the database + */ + fetchByUserIdClientId: ( + userId: string | number, + clientId: string | number + ) => Promise; +} + +/** + * OAuth2 client adapter model + */ +export interface OAuth2ClientAdapter { + /** + * Get client ID from client object + */ + getId: (client: OAuth2Client) => string | number; + + /** + * Get client object from the database by client ID + */ + fetchById: (id: string | number) => Promise; + + /** + * Check the client secret + */ + checkSecret: (client: OAuth2Client, secret: string) => boolean; + + /** + * Check grant type + */ + checkGrantType: (client: OAuth2Client, grant: string) => boolean; + + /** + * Get the redirect uri of a client + */ + getRedirectUri: (client: OAuth2Client) => string; + + /** + * Check the redirect uri against a client + */ + checkRedirectUri: (client: OAuth2Client, redirectUri: string) => boolean; + + /** + * Transform the scope into a string array of scopes + */ + transformScope: (scope: string | string[]) => string[]; + + /** + * Check scopes against client + */ + checkScope: (client: OAuth2Client, scope: string[]) => boolean; +} + +/** + * OAuth2 response code adapter model + */ +export interface OAuth2CodeAdapter { + /** + * Static time-to-live in seconds for code + */ + ttl: number; + + /** + * Create a new code + */ + create: ( + userId: string | number, + clientId: string | number, + scope: string | string[], + ttl: number + ) => Promise; + + /** + * Fetch a code by the code string from the database + */ + fetchByCode: (code: OAuth2Code | string) => Promise; + + /** + * Remove a code + */ + removeByCode: (code: string | OAuth2Code) => Promise; + + /** + * Get user ID from a code object + */ + getUserId: (code: OAuth2Code) => string; + + /** + * Get client ID from a code object + */ + getClientId: (code: OAuth2Code) => string; + + /** + * Get scope from a code object + */ + getScope: (code: OAuth2Code) => string; + + /** + * Check the time-to-live value + */ + checkTTL: (code: OAuth2Code) => boolean; +} + +/** + * OAuth2 refresh token adapter model + */ +export interface OAuth2RefreshTokenAdapter { + /** + * Create a new refresh token + */ + create: ( + userId: string | number, + clientId: string | number, + scope: string | string[] + ) => Promise; + + /** + * Fetch a token from the database + */ + fetchByToken: (token: OAuth2RefreshToken | string) => Promise; + + /** + * Remove refresh token by user ID and client ID + */ + removeByUserIdClientId: ( + userId: string | number, + clientId: string | number + ) => Promise; + + /** + * Remove token by the token itself + */ + removeByRefreshToken: (token: string) => Promise; + + /** + * Get user ID from token + */ + getUserId: (code: OAuth2RefreshToken) => string; + + /** + * Get client ID from token + */ + getClientId: (code: OAuth2RefreshToken) => string; + + /** + * Get scope from token + */ + getScope: (code: OAuth2RefreshToken) => string; +} + +/** + * OAuth2 user adapter model + */ +export interface OAuth2UserAdapter { + /** + * Get user ID + */ + getId: (user: OAuth2User) => string | number; + + /** + * Get user from the database by ID + */ + fetchById: (id: string | number) => Promise; + + /** + * Fetch a user from the database by username + */ + fetchByUsername: (username: string) => Promise; + + /** + * Check user's password for implicit grant + */ + checkPassword: (user: OAuth2User, password: string) => Promise; + + /** + * Fetch the user from the express request + */ + fetchFromRequest: (req: Request) => Promise; + + /** + * Check if the user has already consented to this client and the scopes. Return false to force a decision. + */ + consented: ( + userId: string | number, + clientId: string | number, + scope: string | string[] + ) => Promise; + + /** + * Save a consent + */ + consent: ( + userId: string | number, + clientId: string | number, + scope: string | string[] + ) => Promise; +} + +/** + * Render the OAuth2 decision page + */ +export type RenderOAuth2Decision = ( + req: Request, + res: Response, + client: OAuth2Client, + scope: string[], + user: OAuth2User, + redirectUri: string +) => void; + +/** + * OAuth2 adapter model + */ +export interface OAuth2AdapterModel { + accessToken: OAuth2AccessTokenAdapter; + refreshToken: OAuth2RefreshTokenAdapter; + user: OAuth2UserAdapter; + client: OAuth2ClientAdapter; + code: OAuth2CodeAdapter; +} + +/** + * OAuth2 adapter + */ +export interface OAuth2 { + model: OAuth2AdapterModel; + decision: RenderOAuth2Decision; +} diff --git a/src/provider.ts b/src/provider.ts new file mode 100644 index 0000000..6796dc2 --- /dev/null +++ b/src/provider.ts @@ -0,0 +1,22 @@ +import { RequestHandler } from 'express'; +import * as controller from './controller'; +import { middleware } from './middleware'; +import { RenderOAuth2Decision, OAuth2, OAuth2AdapterModel } from './model/model'; + +export class OAuth2Provider implements OAuth2 { + public bearer = middleware; + public controller = controller; + + constructor ( + public model: OAuth2AdapterModel, + public decision: RenderOAuth2Decision, + ) {} + + express(): RequestHandler { + return (req, _res, next) => { + console.debug('OAuth2 Injected into request'); + req.oauth2 = this; + next(); + } + } +} diff --git a/src/types/express/index.d.ts b/src/types/express/index.d.ts new file mode 100644 index 0000000..d58052e --- /dev/null +++ b/src/types/express/index.d.ts @@ -0,0 +1,15 @@ +import { OAuth2 } from '../../model/model'; + +declare global { + namespace Express { + export interface Request { + oauth2: OAuth2; + } + } +} + +declare module 'express-session' { + interface SessionData { + csrf: string; + } +} diff --git a/src/utils/response.ts b/src/utils/response.ts new file mode 100644 index 0000000..831b493 --- /dev/null +++ b/src/utils/response.ts @@ -0,0 +1,74 @@ +import { Request, Response } from 'express'; +import { OAuth2Error, ServerError } from '../model/error'; +import { OAuth2TokenResponse } from '../model/model'; + +interface ErrorResponseData { + [x: string]: string | undefined; + error: string; + error_description: string; + state?: string; +} + +function dataRes(req: Request, res: Response, code: number, data: any): void { + res.header('Cache-Control', 'no-store'); + res.header('Pragma', 'no-cache'); + res.status(code).send(data); + console.debug('Response:', data); +} + +function redirect(req: Request, res: Response, redirectUri: string): void { + res.header('Location', redirectUri); + res.status(302).end(); + console.debug('Redirecting to', redirectUri); +} + +export function error(req: Request, res: Response, err: OAuth2Error, redirectUri?: string): void { + // Transform unknown error + if (!(err instanceof OAuth2Error)) { + console.error((err as Error).stack); + err = new ServerError('Uncaught exception'); + } else { + console.error('Exception caught', err.stack); + } + + if (redirectUri) { + const obj: ErrorResponseData = { + error: err.code, + error_description: err.message + }; + + if (req.query.state) { + obj.state = req.query.state as string; + } + + redirectUri += '?' + (new URLSearchParams(obj as Record).toString()); + redirect(req, res, redirectUri); + return; + } + + dataRes(req, res, err.status, { error: err.code, error_description: err.message }); +} + +export function data( + req: Request, + res: Response, + obj: OAuth2TokenResponse, + redirectUri?: string, + fragment: boolean = false +): void { + if (redirectUri) { + redirectUri += fragment + ? '#' + : (redirectUri.indexOf('?') === -1 ? '?' : '&'); + + if (req.query.state) { + obj.state = req.query.state as string; + } + + redirectUri += new URLSearchParams(obj as Record).toString(); + redirect(req, res, redirectUri); + return; + } + + dataRes(req, res, 200, obj); +} diff --git a/src/utils/wrap.ts b/src/utils/wrap.ts new file mode 100644 index 0000000..3326f31 --- /dev/null +++ b/src/utils/wrap.ts @@ -0,0 +1,6 @@ +import { RequestHandler } from 'express'; +import { error } from './response'; + +export default (fn: RequestHandler, redir?: boolean): RequestHandler => (req, res, next) => + (fn(req, res, next) as unknown as Promise).catch(e => + error(req, res, e, redir ? (req.query.redirect_uri as string) : undefined)); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..af6d743 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,104 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + "typeRoots": [ + "./node_modules/@types", + "src/types" + ], + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}