From b169e83575dedebee67750295bc7ac082ca433d1 Mon Sep 17 00:00:00 2001 From: eladcon Date: Mon, 25 Mar 2024 16:12:03 +0200 Subject: [PATCH] feat: `bring jwt` (#123) * wip * wip * wip * wip * wip * wip --- .github/workflows/canary.yaml | 21 ++ .github/workflows/jwt-pull.yaml | 29 +++ .github/workflows/jwt-release.yaml | 54 ++++ .mergify.yml | 6 + README.md | 1 + jwt/.gitignore | 2 + jwt/LICENSE | 21 ++ jwt/README.md | 30 +++ jwt/lib.test.w | 38 +++ jwt/lib.w | 103 ++++++++ jwt/package-lock.json | 381 +++++++++++++++++++++++++++++ jwt/package.json | 19 ++ jwt/utils.extern.d.ts | 39 +++ jwt/utils.mts | 10 + mklib.sh | 10 +- 15 files changed, 759 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/jwt-pull.yaml create mode 100644 .github/workflows/jwt-release.yaml create mode 100644 jwt/.gitignore create mode 100644 jwt/LICENSE create mode 100644 jwt/README.md create mode 100644 jwt/lib.test.w create mode 100644 jwt/lib.w create mode 100644 jwt/package-lock.json create mode 100644 jwt/package.json create mode 100644 jwt/utils.extern.d.ts create mode 100644 jwt/utils.mts diff --git a/.github/workflows/canary.yaml b/.github/workflows/canary.yaml index 1cd0e0b8..310dbaae 100644 --- a/.github/workflows/canary.yaml +++ b/.github/workflows/canary.yaml @@ -175,6 +175,27 @@ jobs: - name: Test run: wing test working-directory: github + canary-jwt: + name: Test jwt + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + sparse-checkout: jwt + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.x + registry-url: https://registry.npmjs.org + - name: Install winglang + run: npm i -g winglang + - name: Install dependencies + run: npm install --include=dev + working-directory: jwt + - name: Test + run: wing test + working-directory: jwt canary-lock: name: Test lock runs-on: ubuntu-latest diff --git a/.github/workflows/jwt-pull.yaml b/.github/workflows/jwt-pull.yaml new file mode 100644 index 00000000..b243f0d0 --- /dev/null +++ b/.github/workflows/jwt-pull.yaml @@ -0,0 +1,29 @@ +name: jwt-pull +on: + pull_request: + paths: + - jwt/** +jobs: + build-jwt: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + sparse-checkout: jwt + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.x + registry-url: https://registry.npmjs.org + - name: Install winglang + run: npm i -g winglang + - name: Install dependencies + run: npm install --include=dev + working-directory: jwt + - name: Test + run: wing test + working-directory: jwt + - name: Pack + run: wing pack + working-directory: jwt diff --git a/.github/workflows/jwt-release.yaml b/.github/workflows/jwt-release.yaml new file mode 100644 index 00000000..fc079f17 --- /dev/null +++ b/.github/workflows/jwt-release.yaml @@ -0,0 +1,54 @@ +name: jwt-release +on: + push: + branches: + - main + paths: + - jwt/** + - "!jwt/package-lock.json" +jobs: + build-jwt: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + sparse-checkout: jwt + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.x + registry-url: https://registry.npmjs.org + - name: Install winglang + run: npm i -g winglang + - name: Install dependencies + run: npm install --include=dev + working-directory: jwt + - name: Test + run: wing test + working-directory: jwt + - name: Pack + run: wing pack + working-directory: jwt + - name: Get package version + run: echo WINGLIB_VERSION=$(node -p "require('./package.json').version") >> + "$GITHUB_ENV" + working-directory: jwt + - name: Publish + run: npm publish --access=public --registry https://registry.npmjs.org --tag + latest *.tgz + working-directory: jwt + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Tag commit + uses: tvdias/github-tagger@v0.0.1 + with: + repo-token: ${{ secrets.PROJEN_GITHUB_TOKEN }} + tag: jwt-v${{ env.WINGLIB_VERSION }} + - name: GitHub release + uses: softprops/action-gh-release@v1 + with: + name: jwt v${{ env.WINGLIB_VERSION }} + tag_name: jwt-v${{ env.WINGLIB_VERSION }} + files: "*.tgz" + token: ${{ secrets.PROJEN_GITHUB_TOKEN }} diff --git a/.mergify.yml b/.mergify.yml index 792e993b..f00ed97a 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -59,6 +59,9 @@ pull_request_rules: - -check-failure=build-github - -check-pending=build-github - -check-stale=build-github + - -check-failure=build-jwt + - -check-pending=build-jwt + - -check-stale=build-jwt - -check-failure=build-lock - -check-pending=build-lock - -check-stale=build-lock @@ -131,6 +134,9 @@ pull_request_rules: - -check-failure=build-github - -check-pending=build-github - -check-stale=build-github + - -check-failure=build-jwt + - -check-pending=build-jwt + - -check-stale=build-jwt - -check-failure=build-lock - -check-pending=build-lock - -check-stale=build-lock diff --git a/README.md b/README.md index 4a8a4683..bbd94918 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ publishing them for you. | [eventbridge](./eventbridge) | [@winglibs/eventbridge](https://www.npmjs.com/package/@winglibs/eventbridge) | awscdk, sim, tf-aws | | [fifoqueue](./fifoqueue) | [@winglibs/fifoqueue](https://www.npmjs.com/package/@winglibs/fifoqueue) | sim, tf-aws | | [github](./github) | [@winglibs/github](https://www.npmjs.com/package/@winglibs/github) | * | +| [jwt](./jwt) | [@winglibs/jwt](https://www.npmjs.com/package/@winglibs/jwt) | * | | [lock](./lock) | [@winglibs/lock](https://www.npmjs.com/package/@winglibs/lock) | * | | [messagefanout](./messagefanout) | [@winglibs/messagefanout](https://www.npmjs.com/package/@winglibs/messagefanout) | | | [ngrok](./ngrok) | [@winglibs/ngrok](https://www.npmjs.com/package/@winglibs/ngrok) | * | diff --git a/jwt/.gitignore b/jwt/.gitignore new file mode 100644 index 00000000..297fdef9 --- /dev/null +++ b/jwt/.gitignore @@ -0,0 +1,2 @@ +target/ +node_modules/ diff --git a/jwt/LICENSE b/jwt/LICENSE new file mode 100644 index 00000000..a875f479 --- /dev/null +++ b/jwt/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Wing + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/jwt/README.md b/jwt/README.md new file mode 100644 index 00000000..b12297d1 --- /dev/null +++ b/jwt/README.md @@ -0,0 +1,30 @@ +# jwt + +A Wing library for working with JWT authentication. + +## Prerequisites + +* [winglang](https://winglang.io). + +## Installation + +```sh +npm i @winglibs/jwt +``` + +## Usage + +```js +bring util; +bring jwt; + +test "will sign and verify" { + let id = util.nanoid(); + let token = jwt.sign({ foo: id }, "shhhhh"); + let decoded1 = jwt.verify(token, secret: "shhhhh"); +} +``` + +## License + +This library is licensed under the [MIT License](./LICENSE). diff --git a/jwt/lib.test.w b/jwt/lib.test.w new file mode 100644 index 00000000..6c6a7d69 --- /dev/null +++ b/jwt/lib.test.w @@ -0,0 +1,38 @@ +bring expect; +bring util; +bring "./lib.w" as jwt; + +test "sign and verify" { + let id = util.nanoid(); + let token = jwt.sign({ foo: id }, "shhhhh"); + let decoded1 = jwt.verify(token, secret: "shhhhh"); + expect.equal(decoded1.get("foo").asStr(), id); + + let token2 = jwt.sign({ foo: id }, "shhhhh", algorithm: "HS256"); + let decoded2 = jwt.verify(token2, secret: "shhhhh", options: { algorithms: ["HS256"] }); + expect.equal(decoded2.get("foo").asStr(), id); +} + +test "sign with notBefore" { + try { + let id = util.nanoid(); + let token = jwt.sign({ foo: id }, "shhhhh", { notBefore: 50m }); + let decoded1 = jwt.verify(token, secret: "shhhhh"); + expect.equal("not-id", id); + log(Json.stringify(decoded1)); + } catch e { + expect.equal(e, "jwt not active"); + } +} + +test "sign with expiresIn" { + try { + let id = util.nanoid(); + let token = jwt.sign({ foo: id }, "shhhhh", { expiresIn: 0s }); + let decoded1 = jwt.verify(token, secret: "shhhhh"); + expect.equal("not-id", id); + log(Json.stringify(decoded1)); + } catch e { + expect.equal(e, "jwt expired"); + } +} diff --git a/jwt/lib.w b/jwt/lib.w new file mode 100644 index 00000000..0600f757 --- /dev/null +++ b/jwt/lib.w @@ -0,0 +1,103 @@ +pub struct VerifyJwtOptions { + algorithms: Array?; + audience: str?; + issuer: str?; + ignoreExpiration: bool?; + ignoreNotBefore: bool?; + jwtid: str?; + nonce: str?; + subject: str?; + maxAge: str?; +} + +pub struct VerifyOptions { + secret: str?; + jwksUri: str?; + options: VerifyJwtOptions?; +} + +pub struct SignOptions { + algorithm: str?; + keyid: str?; + expiresIn: duration?; + notBefore: duration?; + audience: Array?; + subject: str?; + issuer: str?; + jwtid: str?; + encoding: str?; +} + +struct JwtHeader { + alg: str?; + typ: str?; + cty: str?; + crit: Array?; + kid: str?; + jku: str?; + x5u: str?; + x5t: str?; + x5c: str?; +} + +struct IJwksClientOptions { + jwksUri: str; +} + +interface IJwksSigningKey { + inflight getPublicKey(): str; +} + +interface IJwksClient { + inflight getSigningKey(kid: str?): IJwksSigningKey; +} + +interface IJwt { + inflight jwksClient(options: IJwksClientOptions): IJwksClient; + inflight sign(data: Json, secret: str, options: Json?): str; + inflight verify(token: str, secret: inflight (JwtHeader, inflight (str, str): void): void, options: VerifyJwtOptions?): Json; +} + +class JwtUtil { + extern "./utils.mts" pub static inflight _jwt(): IJwt; +} + +pub class Util { + pub inflight static sign(data: Json, secret: str, options: SignOptions?): str { + let var opts: MutJson? = nil; + if let options = options { + opts = MutJson Json.parse(Json.stringify(options)); + if let expiresIn = options.expiresIn { + opts?.set("expiresIn", expiresIn.seconds); + } + if let notBefore = options.notBefore { + opts?.set("notBefore", notBefore.seconds); + } + } + return JwtUtil._jwt().sign(data, secret, opts); + } + + pub inflight static verify(token: str, options: VerifyOptions): Json { + if let secret = options.secret { + let getKey = inflight (header: JwtHeader, callback: inflight (str, str): void) => { + callback(unsafeCast(nil), secret); + }; + let decoded = JwtUtil._jwt().verify(token, getKey, options.options); + return decoded; + } elif let jwksUri = options.jwksUri { + let client = JwtUtil._jwt().jwksClient(jwksUri: jwksUri); + let getKey = inflight (header: JwtHeader, callback: inflight (str, str): void) => { + try { + let secret = client.getSigningKey(header.kid).getPublicKey(); + callback(unsafeCast(nil), secret); + } catch error { + callback(error, unsafeCast(nil)); + } + }; + let decoded = JwtUtil._jwt().verify(token, getKey, options.options); + return decoded; + } else { + throw "Either secret or jwksUri must be provided"; + } + } +} diff --git a/jwt/package-lock.json b/jwt/package-lock.json new file mode 100644 index 00000000..10a4526f --- /dev/null +++ b/jwt/package-lock.json @@ -0,0 +1,381 @@ +{ + "name": "@winglibs/jwt", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@winglibs/jwt", + "version": "0.0.1", + "license": "MIT", + "peerDependencies": { + "jsonwebtoken": "^9.0.2", + "jwks-rsa": "^3.1.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "peer": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "peer": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "peer": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "peer": true + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", + "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "peer": true + }, + "node_modules/@types/node": { + "version": "20.11.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", + "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.9.12", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.12.tgz", + "integrity": "sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==", + "peer": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "peer": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "peer": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "peer": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "peer": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "peer": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "peer": true + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jose": { + "version": "4.15.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", + "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "peer": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "peer": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", + "peer": true, + "dependencies": { + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", + "debug": "^4.3.4", + "jose": "^4.14.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "peer": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "peer": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "peer": true + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "peer": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "peer": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "peer": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "peer": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "peer": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "peer": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "peer": true + }, + "node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "peer": true, + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", + "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", + "peer": true, + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "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==", + "peer": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "peer": true + }, + "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" + } + ], + "peer": true + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "peer": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "peer": true + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "peer": true + } + } +} diff --git a/jwt/package.json b/jwt/package.json new file mode 100644 index 00000000..b66a85ba --- /dev/null +++ b/jwt/package.json @@ -0,0 +1,19 @@ +{ + "name": "@winglibs/jwt", + "description": "Wing library for JWT authentication", + "version": "0.0.1", + "repository": { + "type": "git", + "url": "https://github.com/winglang/winglibs.git", + "directory": "jwt" + }, + "author": { + "email": "eladc@wing.cloud", + "name": "Elad Cohen" + }, + "license": "MIT", + "peerDependencies": { + "jsonwebtoken": "^9.0.2", + "jwks-rsa": "^3.1.0" + } +} diff --git a/jwt/utils.extern.d.ts b/jwt/utils.extern.d.ts new file mode 100644 index 00000000..809812eb --- /dev/null +++ b/jwt/utils.extern.d.ts @@ -0,0 +1,39 @@ +export default interface extern { + _jwt: () => Promise, +} +export interface IJwksClientOptions { + readonly jwksUri: string; +} +export interface IJwksSigningKey$Inflight { + readonly getPublicKey: () => Promise; +} +export interface IJwksClient$Inflight { + readonly getSigningKey: (kid?: (string) | undefined) => Promise; +} +export interface JwtHeader { + readonly alg?: (string) | undefined; + readonly crit?: ((readonly (string)[])) | undefined; + readonly cty?: (string) | undefined; + readonly jku?: (string) | undefined; + readonly kid?: (string) | undefined; + readonly typ?: (string) | undefined; + readonly x5c?: (string) | undefined; + readonly x5t?: (string) | undefined; + readonly x5u?: (string) | undefined; +} +export interface VerifyJwtOptions { + readonly algorithms?: ((readonly (string)[])) | undefined; + readonly audience?: (string) | undefined; + readonly ignoreExpiration?: (boolean) | undefined; + readonly ignoreNotBefore?: (boolean) | undefined; + readonly issuer?: (string) | undefined; + readonly jwtid?: (string) | undefined; + readonly maxAge?: (string) | undefined; + readonly nonce?: (string) | undefined; + readonly subject?: (string) | undefined; +} +export interface IJwt$Inflight { + readonly jwksClient: (options: IJwksClientOptions) => Promise; + readonly sign: (data: Readonly, secret: string, options?: (Readonly) | undefined) => Promise; + readonly verify: (token: string, secret: (arg0: JwtHeader, arg1: (arg0: string, arg1: string) => Promise) => Promise, options?: (VerifyJwtOptions) | undefined) => Promise>; +} \ No newline at end of file diff --git a/jwt/utils.mts b/jwt/utils.mts new file mode 100644 index 00000000..f5cabe4c --- /dev/null +++ b/jwt/utils.mts @@ -0,0 +1,10 @@ +import { promisify } from "util"; +import jwt from "jsonwebtoken"; +import jwksClient from "jwks-rsa"; + +export const _jwt = () => ({ + sign: promisify(jwt.sign), + verify: promisify(jwt.verify), + jwksClient: jwksClient, +}); + diff --git a/mklib.sh b/mklib.sh index c811ac5d..2cf3a151 100755 --- a/mklib.sh +++ b/mklib.sh @@ -57,17 +57,17 @@ cat > $1/README.md <