diff --git a/package-lock.json b/package-lock.json index 378c28ee9a7..6ecf2815e20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11429,31 +11429,35 @@ } }, "node_modules/camelcase-keys": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", - "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", "dependencies": { - "camelcase": "^6.3.0", - "map-obj": "^4.1.0", - "quick-lru": "^5.1.1", - "type-fest": "^1.2.1" + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/camelcase-keys/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" + } + }, + "node_modules/camelcase-keys/node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "engines": { + "node": ">=8" } }, "node_modules/caniuse-api": { @@ -24912,41 +24916,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/meow/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/meow/node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/meow/node_modules/type-fest": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", @@ -29099,9 +29068,9 @@ } }, "node_modules/query-string": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.0.tgz", - "integrity": "sha512-wnJ8covk+S9isYR5JIXPt93kFUmI2fQ4R/8130fuq+qwLiGVTurg7Klodgfw4NSz/oe7xnyi09y3lSrogUeM3g==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz", + "integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", "dependencies": { "decode-uri-component": "^0.2.0", "filter-obj": "^1.1.0", @@ -35224,10 +35193,10 @@ }, "packages/backend-core": { "name": "@clerk/backend-core", - "version": "1.13.0", + "version": "1.14.1", "license": "MIT", "dependencies": { - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "query-string": "^7.0.1", "snakecase-keys": "^5.1.2", "tslib": "^2.3.1" @@ -35235,9 +35204,7 @@ "devDependencies": { "@peculiar/webcrypto": "^1.3.2", "@types/jest": "^27.4.0", - "@types/node": "^16.11.12", "@types/node-fetch": "^2", - "got": "^11.0.0", "jest": "^27.4.7", "nock": "^13.2.1", "node-fetch": "^2.6.0", @@ -35245,18 +35212,12 @@ "typescript": "^4.6.4" } }, - "packages/backend-core/node_modules/@types/node": { - "version": "16.11.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.22.tgz", - "integrity": "sha512-DYNtJWauMQ9RNpesl4aVothr97/tIJM8HbyOXJ0AYT1Z2bEjLHyfjOBPAQQVMLf8h3kSShYfNk8Wnto8B2zHUA==", - "dev": true - }, "packages/clerk-js": { "name": "@clerk/clerk-js", - "version": "3.16.4", + "version": "3.17.0", "license": "MIT", "dependencies": { - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "@emotion/cache": "^11.7.1", "@emotion/react": "^11.9.0", "@popperjs/core": "^2.4.4", @@ -35278,7 +35239,7 @@ "@babel/preset-env": "^7.12.1", "@babel/preset-react": "^7.12.5", "@babel/preset-typescript": "^7.12.1", - "@clerk/shared": "^0.3.6", + "@clerk/shared": "^0.3.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.6", "@svgr/webpack": "^6.2.1", "@testing-library/dom": "^7.28.1", @@ -35398,11 +35359,11 @@ }, "packages/edge": { "name": "@clerk/edge", - "version": "1.6.1", + "version": "1.7.1", "license": "MIT", "dependencies": { - "@clerk/backend-core": "^1.13.0", - "@clerk/types": "^2.20.0", + "@clerk/backend-core": "^1.14.1", + "@clerk/types": "^2.21.0", "@peculiar/webcrypto": "^1.2.3", "next": "^12.2.0" }, @@ -35696,15 +35657,15 @@ }, "packages/expo": { "name": "@clerk/clerk-expo", - "version": "0.9.36", + "version": "0.9.37", "license": "MIT", "dependencies": { - "@clerk/clerk-js": "^3.16.4", - "@clerk/clerk-react": "^3.5.0", + "@clerk/clerk-js": "^3.17.0", + "@clerk/clerk-react": "^3.5.1", "base-64": "^1.0.0" }, "devDependencies": { - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "@types/jest": "^27.4.0", "@types/node": "^16.11.9", "@types/react": "^17.0.39", @@ -35751,24 +35712,23 @@ } }, "packages/gatsby-plugin-clerk/node_modules/@clerk/backend-core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@clerk/backend-core/-/backend-core-1.5.0.tgz", - "integrity": "sha512-WL7+gRqe5qLocC5uk6IuNi8GdcNGoTZkK4TGrEHe/RIr7T201+PMkPGAAV75bjog3ruUTYbsoZRIvmI2hvAt4A==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@clerk/backend-core/-/backend-core-1.13.0.tgz", + "integrity": "sha512-5JZvZuiw5rN9E2DKcgi0/kt28yprWLV07p9WYcm3IsvS9GAacmvK/LP5cwPqkjXhauqBBWUfKaApXViXZ0Vsqw==", "dependencies": { - "@clerk/types": "^2.7.0", - "camelcase-keys": "^7.0.1", + "@clerk/types": "^2.20.0", "query-string": "^7.0.1", "snakecase-keys": "^5.1.2", "tslib": "^2.3.1" } }, "packages/gatsby-plugin-clerk/node_modules/@clerk/clerk-sdk-node": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@clerk/clerk-sdk-node/-/clerk-sdk-node-3.3.5.tgz", - "integrity": "sha512-i4l40/0Bj/FDQbAo6CGoimWvGbgAZe7xzZKck5jeva8F/WDf+5nPmgaJYQxrjCMK2i24ZaeC4tv4/q+dZqzMsw==", + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/@clerk/clerk-sdk-node/-/clerk-sdk-node-3.8.6.tgz", + "integrity": "sha512-Q3WxIbfIaKt3ZYqfJP+FbjXaT4gGn9wFg7Xk8znWXewDf8M9Wrouq5UmVsF2uEfvV3mWMdo+pa6JDI2AXVizAA==", "dependencies": { - "@clerk/backend-core": "^1.5.0", - "@clerk/types": "^2.7.0", + "@clerk/backend-core": "^1.13.0", + "@clerk/types": "^2.20.0", "@peculiar/webcrypto": "^1.2.3", "camelcase-keys": "^6.2.2", "cookies": "^0.8.0", @@ -35783,22 +35743,6 @@ "node": ">=12" } }, - "packages/gatsby-plugin-clerk/node_modules/@clerk/clerk-sdk-node/node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/gatsby-plugin-clerk/node_modules/@clerk/clerk-sdk-node/node_modules/snakecase-keys": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-3.2.1.tgz", @@ -35823,14 +35767,6 @@ "integrity": "sha512-C1pD3kgLoZ56Uuy5lhfOxie4aZlA3UMGLX9rXteq4WitEZH6Rl80mwactt9QG0w0gLFlN/kLBTFnGXtDVWvWQw==", "dev": true }, - "packages/gatsby-plugin-clerk/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, "packages/gatsby-plugin-clerk/node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -35839,23 +35775,15 @@ "node": ">= 0.6" } }, - "packages/gatsby-plugin-clerk/node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "engines": { - "node": ">=8" - } - }, "packages/nextjs": { "name": "@clerk/nextjs", - "version": "3.8.0", + "version": "3.8.2", "license": "MIT", "dependencies": { - "@clerk/clerk-react": "^3.5.0", - "@clerk/clerk-sdk-node": "^3.8.6", - "@clerk/edge": "^1.6.1", - "@clerk/types": "^2.20.0", + "@clerk/clerk-react": "^3.5.1", + "@clerk/clerk-sdk-node": "^3.9.1", + "@clerk/edge": "^1.7.1", + "@clerk/types": "^2.21.0", "tslib": "^2.3.1" }, "devDependencies": { @@ -35885,10 +35813,10 @@ }, "packages/react": { "name": "@clerk/clerk-react", - "version": "3.5.0", + "version": "3.5.1", "license": "MIT", "dependencies": { - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "swr": "^1.3.0", "tslib": "^2.3.1" }, @@ -35926,12 +35854,12 @@ }, "packages/remix": { "name": "@clerk/remix", - "version": "0.5.11", + "version": "0.5.13", "license": "MIT", "dependencies": { - "@clerk/clerk-react": "^3.5.0", - "@clerk/clerk-sdk-node": "^3.8.6", - "@clerk/types": "^2.20.0", + "@clerk/clerk-react": "^3.5.1", + "@clerk/clerk-sdk-node": "^3.9.1", + "@clerk/types": "^2.21.0", "cookie": "^0.5.0", "tslib": "^2.3.1" }, @@ -35977,18 +35905,18 @@ }, "packages/sdk-node": { "name": "@clerk/clerk-sdk-node", - "version": "3.8.6", + "version": "3.9.1", "license": "MIT", "dependencies": { - "@clerk/backend-core": "^1.13.0", - "@clerk/types": "^2.20.0", + "@clerk/backend-core": "^1.14.1", + "@clerk/types": "^2.21.0", "@peculiar/webcrypto": "^1.2.3", "camelcase-keys": "^6.2.2", "cookies": "^0.8.0", "deepmerge": "^4.2.2", - "got": "^11.8.2", "jsonwebtoken": "^8.5.1", "jwks-rsa": "^2.0.4", + "node-fetch": "^2.6.0", "snakecase-keys": "^3.2.1", "tslib": "^2.3.1" }, @@ -35997,6 +35925,7 @@ "@types/express": "^4.17.11", "@types/jest": "^27.4.0", "@types/jsonwebtoken": "^8.5.6", + "@types/node-fetch": "^2", "jest": "^27.4.7", "nock": "^13.0.7", "npm-run-all": "^4.1.5", @@ -36005,39 +35934,15 @@ "typescript": "^4.6.4" }, "engines": { - "node": ">=12" - } - }, - "packages/sdk-node/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "packages/sdk-node/node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14" } }, - "packages/sdk-node/node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "packages/sdk-node/node_modules/node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", "engines": { - "node": ">=8" + "node": "4.x || >=6.0.0" } }, "packages/sdk-node/node_modules/snakecase-keys": { @@ -36054,12 +35959,12 @@ }, "packages/shared": { "name": "@clerk/shared", - "version": "0.3.6", + "version": "0.3.7", "devDependencies": { "@babel/core": "^7.13.14", "@babel/preset-env": "^7.13.12", "@babel/preset-react": "^7.13.13", - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "@popperjs/core": "^2.5.4", "@sentry/browser": "^6.3.0", "@svgr/webpack": "^6.2.1", @@ -36113,7 +36018,7 @@ }, "packages/types": { "name": "@clerk/types", - "version": "2.20.0", + "version": "2.21.0", "license": "MIT", "devDependencies": { "@types/jest": "^27.4.0", @@ -37354,12 +37259,10 @@ "@clerk/backend-core": { "version": "file:packages/backend-core", "requires": { - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "@peculiar/webcrypto": "^1.3.2", "@types/jest": "^27.4.0", - "@types/node": "^16.11.12", "@types/node-fetch": "^2", - "got": "^11.0.0", "jest": "^27.4.7", "nock": "^13.2.1", "node-fetch": "^2.6.0", @@ -37368,22 +37271,14 @@ "ts-jest": "^27.1.3", "tslib": "^2.3.1", "typescript": "^4.6.4" - }, - "dependencies": { - "@types/node": { - "version": "16.11.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.22.tgz", - "integrity": "sha512-DYNtJWauMQ9RNpesl4aVothr97/tIJM8HbyOXJ0AYT1Z2bEjLHyfjOBPAQQVMLf8h3kSShYfNk8Wnto8B2zHUA==", - "dev": true - } } }, "@clerk/clerk-expo": { "version": "file:packages/expo", "requires": { - "@clerk/clerk-js": "^3.16.4", - "@clerk/clerk-react": "^3.5.0", - "@clerk/types": "^2.20.0", + "@clerk/clerk-js": "^3.17.0", + "@clerk/clerk-react": "^3.5.1", + "@clerk/types": "^2.21.0", "@types/jest": "^27.4.0", "@types/node": "^16.11.9", "@types/react": "^17.0.39", @@ -37414,8 +37309,8 @@ "@babel/preset-env": "^7.12.1", "@babel/preset-react": "^7.12.5", "@babel/preset-typescript": "^7.12.1", - "@clerk/shared": "^0.3.6", - "@clerk/types": "^2.20.0", + "@clerk/shared": "^0.3.7", + "@clerk/types": "^2.21.0", "@emotion/cache": "^11.7.1", "@emotion/react": "^11.9.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.6", @@ -37525,7 +37420,7 @@ "@clerk/clerk-react": { "version": "file:packages/react", "requires": { - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "@testing-library/dom": "^7.28.1", "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.1", @@ -37558,21 +37453,22 @@ "@clerk/clerk-sdk-node": { "version": "file:packages/sdk-node", "requires": { - "@clerk/backend-core": "^1.13.0", - "@clerk/types": "^2.20.0", + "@clerk/backend-core": "^1.14.1", + "@clerk/types": "^2.21.0", "@peculiar/webcrypto": "^1.2.3", "@types/cookies": "^0.7.7", "@types/express": "^4.17.11", "@types/jest": "^27.4.0", "@types/jsonwebtoken": "^8.5.6", + "@types/node-fetch": "^2", "camelcase-keys": "^6.2.2", "cookies": "^0.8.0", "deepmerge": "^4.2.2", - "got": "^11.8.2", "jest": "^27.4.7", "jsonwebtoken": "^8.5.1", "jwks-rsa": "^2.0.4", "nock": "^13.0.7", + "node-fetch": "^2.6.0", "npm-run-all": "^4.1.5", "prettier": "^2.5.0", "snakecase-keys": "^3.2.1", @@ -37581,25 +37477,10 @@ "typescript": "^4.6.4" }, "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==" + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, "snakecase-keys": { "version": "3.2.1", @@ -37615,8 +37496,8 @@ "@clerk/edge": { "version": "file:packages/edge", "requires": { - "@clerk/backend-core": "^1.13.0", - "@clerk/types": "^2.20.0", + "@clerk/backend-core": "^1.14.1", + "@clerk/types": "^2.21.0", "@peculiar/webcrypto": "^1.2.3", "@types/jest": "^27.4.0", "@types/node": "^16.11.12", @@ -37763,10 +37644,10 @@ "@clerk/nextjs": { "version": "file:packages/nextjs", "requires": { - "@clerk/clerk-react": "^3.5.0", - "@clerk/clerk-sdk-node": "^3.8.6", - "@clerk/edge": "^1.6.1", - "@clerk/types": "^2.20.0", + "@clerk/clerk-react": "^3.5.1", + "@clerk/clerk-sdk-node": "^3.9.1", + "@clerk/edge": "^1.7.1", + "@clerk/types": "^2.21.0", "@types/jest": "^27.4.0", "@types/node": "^16.11.9", "@types/react": "^17.0.39", @@ -37791,9 +37672,9 @@ "@clerk/remix": { "version": "file:packages/remix", "requires": { - "@clerk/clerk-react": "^3.5.0", - "@clerk/clerk-sdk-node": "^3.8.6", - "@clerk/types": "^2.20.0", + "@clerk/clerk-react": "^3.5.1", + "@clerk/clerk-sdk-node": "^3.9.1", + "@clerk/types": "^2.21.0", "@types/cookie": "^0.5.0", "@types/jest": "^27.4.0", "@types/node": "^16.11.9", @@ -37833,7 +37714,7 @@ "@babel/core": "^7.13.14", "@babel/preset-env": "^7.13.12", "@babel/preset-react": "^7.13.13", - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "@popperjs/core": "^2.5.4", "@sentry/browser": "^6.3.0", "@svgr/webpack": "^6.2.1", @@ -45228,20 +45109,24 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" }, "camelcase-keys": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", - "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", "requires": { - "camelcase": "^6.3.0", - "map-obj": "^4.1.0", - "quick-lru": "^5.1.1", - "type-fest": "^1.2.1" + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" }, "dependencies": { - "type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==" } } }, @@ -50937,24 +50822,23 @@ }, "dependencies": { "@clerk/backend-core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@clerk/backend-core/-/backend-core-1.5.0.tgz", - "integrity": "sha512-WL7+gRqe5qLocC5uk6IuNi8GdcNGoTZkK4TGrEHe/RIr7T201+PMkPGAAV75bjog3ruUTYbsoZRIvmI2hvAt4A==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@clerk/backend-core/-/backend-core-1.13.0.tgz", + "integrity": "sha512-5JZvZuiw5rN9E2DKcgi0/kt28yprWLV07p9WYcm3IsvS9GAacmvK/LP5cwPqkjXhauqBBWUfKaApXViXZ0Vsqw==", "requires": { - "@clerk/types": "^2.7.0", - "camelcase-keys": "^7.0.1", + "@clerk/types": "^2.20.0", "query-string": "^7.0.1", "snakecase-keys": "^5.1.2", "tslib": "^2.3.1" } }, "@clerk/clerk-sdk-node": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@clerk/clerk-sdk-node/-/clerk-sdk-node-3.3.5.tgz", - "integrity": "sha512-i4l40/0Bj/FDQbAo6CGoimWvGbgAZe7xzZKck5jeva8F/WDf+5nPmgaJYQxrjCMK2i24ZaeC4tv4/q+dZqzMsw==", + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/@clerk/clerk-sdk-node/-/clerk-sdk-node-3.8.6.tgz", + "integrity": "sha512-Q3WxIbfIaKt3ZYqfJP+FbjXaT4gGn9wFg7Xk8znWXewDf8M9Wrouq5UmVsF2uEfvV3mWMdo+pa6JDI2AXVizAA==", "requires": { - "@clerk/backend-core": "^1.5.0", - "@clerk/types": "^2.7.0", + "@clerk/backend-core": "^1.13.0", + "@clerk/types": "^2.20.0", "@peculiar/webcrypto": "^1.2.3", "camelcase-keys": "^6.2.2", "cookies": "^0.8.0", @@ -50966,16 +50850,6 @@ "tslib": "^2.3.1" }, "dependencies": { - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, "snakecase-keys": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-3.2.1.tgz", @@ -50999,20 +50873,10 @@ "integrity": "sha512-C1pD3kgLoZ56Uuy5lhfOxie4aZlA3UMGLX9rXteq4WitEZH6Rl80mwactt9QG0w0gLFlN/kLBTFnGXtDVWvWQw==", "dev": true }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, "cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==" } } }, @@ -55543,29 +55407,6 @@ "yargs-parser": "^20.2.3" }, "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - }, "type-fest": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", @@ -58620,9 +58461,9 @@ } }, "query-string": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.0.tgz", - "integrity": "sha512-wnJ8covk+S9isYR5JIXPt93kFUmI2fQ4R/8130fuq+qwLiGVTurg7Klodgfw4NSz/oe7xnyi09y3lSrogUeM3g==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz", + "integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", "requires": { "decode-uri-component": "^0.2.0", "filter-obj": "^1.1.0", diff --git a/packages/backend-core/API.md b/packages/backend-core/API.md index 01dfc60c5b5..c2cf1b465ad 100644 --- a/packages/backend-core/API.md +++ b/packages/backend-core/API.md @@ -32,11 +32,19 @@ Reference of the methods supported in the Clerk Backend API wrapper. [API refere - [createOrganizationMembership(params)](#createorganizationmembershipparams) - [updateOrganizationMembership(params)](#updateorganizationmembershipparams) - [deleteOrganizationMembership(params)](#deleteorganizationmembershipparams) +- [Redirect URLs](#redirect_urls) + - [createRedirectUrl](#create-redirect-url) + - [getRedirectUrlList](#get-redirect-url-list) + - [getRedirectUrl](#get-redirect-url) + - [deleteRedirectUrl](#delete-redirect-url) - [Session operations](#session-operations) - [getSessionList({ clientId, userId })](#getsessionlist-clientid-userid-) - [getSession(sessionId)](#getsessionsessionid) - [revokeSession(sessionId)](#revokesessionsessionid) - [verifySession(sessionId, sessionToken)](#verifysessionsessionid-sessiontoken) +- [Sign in tokens](#sign-in-tokens) + - [createSignInToken({ user_id, expires_in_seconds })](#create-sign-in-token) + - [revokeSignInToken(tokenId)](#revoke-sign-in-token) - [User operations](#user-operations) - [getUserList()](#getuserlist) - [getUser(userId)](#getuseruserid) @@ -382,6 +390,44 @@ const membership = await clerkAPI.organizations.deleteOrganizationMembership({ }); ``` +## Redirect URLs operations + +Redirect URLs endpoints are used to whitelist URLs for native application authentication flows such as OAuth sign-ins and sign-ups in [React Native](https://clerk.dev/docs/reference/clerk-expo) and [Expo](https://clerk.dev/docs/reference/clerk-expo). + +Redirect URL operations are exposed by the `redirectUrls` sub-api (`clerkAPI.redirectUrls`). + +#### createRedirectUrl({ url }) + +Creates a new redirect URL: + +```ts +const redirectUrl = await clerkAPI.redirectUrls.createRedirectUrl({ url }); +``` + +#### getRedirectUrlList() + +Get the list of all redirect URLs: + +```ts +const redirectUrlList = await clerkAPI.redirectUrls.getRedirectUrlList(); +``` + +#### getRedirectUrl(redirectUrlId) + +Retrieve a redirect URL: + +```ts +const redirectUrl = await clerkAPI.redirectUrls.getRedirectUrl('redirect_url_test'); +``` + +#### deleteRedirectUrl(redirectUrlId) + +Delete a redirect URL: + +```ts +await clerkAPI.redirectUrls.deleteRedirectUrl('redirect_url_test'); +``` + ## Session operations Session operations are exposed by the `sessions` sub-api (`clerkAPI.sessions`). @@ -434,6 +480,28 @@ const sessionToken = 'my-session-token'; const session = await clerkAPI.sessions.verifySession(sessionId, sessionToken); ``` +## Sign in token operations + +Generate a token for an existing user to sign in without him needing to apply any first-factor type authentication. + +_Second-factor type inputs would still need to be filled._ + +#### createSignInToken(params) + +Creates a sign in token: + +```ts +const signInToken = await clerkAPI.signInTokens.createSignInToken({ userId: 'user_test_id', expiresInSeconds: 60 }); +``` + +#### revokeSignInToken(signInTokenId) + +Revokes an issued sign in token. + +```ts +await clerkAPI.signInTokens.revokeSignInToken('token_test_id'); +``` + ## User operations User operations are exposed by the `users` sub-api (`clerkAPI.users`). diff --git a/packages/backend-core/CHANGELOG.md b/packages/backend-core/CHANGELOG.md index 0566f1a3fc4..6c3a2de267a 100644 --- a/packages/backend-core/CHANGELOG.md +++ b/packages/backend-core/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [1.14.1](https://github.com/clerkinc/javascript/compare/@clerk/backend-core@1.14.0...@clerk/backend-core@1.14.1) (2022-08-04) + +**Note:** Version bump only for package @clerk/backend-core + +## [1.14.0](https://github.com/clerkinc/javascript/compare/@clerk/backend-core@1.14.0-staging.0...@clerk/backend-core@1.14.0) (2022-07-26) + +**Note:** Version bump only for package @clerk/backend-core + ## [1.13.0](https://github.com/clerkinc/javascript/compare/@clerk/backend-core@1.12.0...@clerk/backend-core@1.13.0) (2022-07-13) ### Features diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index aef03755fd3..759d8deff19 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/backend-core", - "version": "1.13.0", + "version": "1.14.1", "license": "MIT", "description": "Clerk Backend API core resources and authentication utilities for JavaScript environments.", "scripts": { @@ -12,7 +12,7 @@ "main": "dist/cjs/index.js", "module": "dist/mjs/index.js", "dependencies": { - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "query-string": "^7.0.1", "snakecase-keys": "^5.1.2", "tslib": "^2.3.1" @@ -20,9 +20,7 @@ "devDependencies": { "@peculiar/webcrypto": "^1.3.2", "@types/jest": "^27.4.0", - "@types/node": "^16.11.12", "@types/node-fetch": "^2", - "got": "^11.0.0", "jest": "^27.4.7", "nock": "^13.2.1", "node-fetch": "^2.6.0", diff --git a/packages/backend-core/src/Base.ts b/packages/backend-core/src/Base.ts index ec3a9abe40e..43cfd9271e7 100644 --- a/packages/backend-core/src/Base.ts +++ b/packages/backend-core/src/Base.ts @@ -116,13 +116,13 @@ export class Base { e: 'AQAB', }; - // Algorithm https://developer.mozilla.org/en-US/docs/Web/API/RsaHashedImportParams + // Algorithm https://developer.mozilla.org/en-US/docs/Web/api/RsaHashedImportParams const algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256', }; - // Based on https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#subjectpublickeyinfo_import + // Based on https://developer.mozilla.org/en-US/docs/Web/api/SubtleCrypto/importKey#subjectpublickeyinfo_import try { return this.importKeyFunction(jwk, algorithm); } catch (_) { diff --git a/packages/backend-core/src/__tests__/TestBackendAPI.ts b/packages/backend-core/src/__tests__/TestBackendAPI.ts deleted file mode 100644 index c80dbd253a5..00000000000 --- a/packages/backend-core/src/__tests__/TestBackendAPI.ts +++ /dev/null @@ -1,29 +0,0 @@ -import fetch from 'node-fetch'; - -import { ClerkBackendAPI } from '../api/ClerkBackendAPI'; - -const defaultApiKey = process.env.CLERK_API_KEY || ''; -const defaultApiVersion = process.env.CLERK_API_VERSION || 'v1'; -const defaultServerApiUrl = process.env.CLERK_API_URL || 'https://api.clerk.dev'; - -export const TestBackendAPIClient = new ClerkBackendAPI({ - apiKey: defaultApiKey, - apiVersion: defaultApiVersion, - serverApiUrl: defaultServerApiUrl, - libName: 'test', - libVersion: '0.0.1', - packageRepo: 'test', - fetcher: async (url, { method, authorization, contentType, userAgent, body }) => { - return ( - await fetch(url, { - method, - headers: { - Authorization: authorization, - 'Content-Type': contentType, - 'User-Agent': userAgent, - }, - ...(body && { body: JSON.stringify(body) }), - }) - ).json(); - }, -}); diff --git a/packages/backend-core/src/__tests__/TestClerkApi.ts b/packages/backend-core/src/__tests__/TestClerkApi.ts new file mode 100644 index 00000000000..b5aa0f4f7f0 --- /dev/null +++ b/packages/backend-core/src/__tests__/TestClerkApi.ts @@ -0,0 +1,52 @@ +import fetch from 'node-fetch'; + +import { ClerkBackendAPI } from '../api/ClerkBackendApi'; +import { ClerkAPIResponseError } from '../api/errors'; + +const defaultAPIKey = process.env.CLERK_API_KEY || ''; +const defaultAPIVersion = process.env.CLERK_API_VERSION || 'v1'; + +export const defaultServerAPIUrl = process.env.CLERK_API_URL || 'https://api.clerk.dev'; + +export const TestClerkAPI = new ClerkBackendAPI({ + apiKey: defaultAPIKey, + apiVersion: defaultAPIVersion, + apiUrl: defaultServerAPIUrl, + libName: 'test', + libVersion: '0.0.1', + apiClient: { + async request({ url, method, queryParams, headerParams, bodyParams }) { + // Build final URL with search parameters + const finalUrl = new URL(url || ''); + + if (queryParams) { + for (const [key, val] of Object.entries(queryParams as Record)) { + // Support array values for queryParams such as { foo: [42, 43] } + if (val) { + [val].flat().forEach(v => finalUrl.searchParams.append(key, v)); + } + } + } + + const response = await fetch(finalUrl.href, { + method, + headers: headerParams as Record, + ...(bodyParams && Object.keys(bodyParams).length > 0 && { body: JSON.stringify(bodyParams) }), + }); + + // Parse JSON or Text response. + const isJSONResponse = headerParams && headerParams['Content-Type'] === 'application/json'; + const data = await (isJSONResponse ? response.json() : response.text()); + + // Check for errors + if (!response.ok) { + throw new ClerkAPIResponseError(response.statusText, { + data: data?.errors || data, + status: response.status, + }); + } + + return data; + }, + }, +}); diff --git a/packages/backend-core/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/backend-core/src/__tests__/__snapshots__/exports.test.ts.snap index 47ba6e6d195..a39b39d58c7 100644 --- a/packages/backend-core/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/backend-core/src/__tests__/__snapshots__/exports.test.ts.snap @@ -37,6 +37,7 @@ Object { "SignedOut": "Signed out", }, "Base": [Function], + "ClerkAPIResponseError": [Function], "ClerkBackendAPI": [Function], "Client": [Function], "Email": [Function], @@ -44,6 +45,7 @@ Object { "ExternalAccount": [Function], "IdentificationLink": [Function], "Invitation": [Function], + "Logger": [Function], "ObjectType": Object { "AllowlistIdentifier": "allowlist_identifier", "Client": "client", @@ -57,8 +59,10 @@ Object { "OrganizationInvitation": "organization_invitation", "OrganizationMembership": "organization_membership", "PhoneNumber": "phone_number", + "RedirectUrl": "redirect_url", "Session": "session", "SignInAttempt": "sign_in_attempt", + "SignInToken": "sign_in_token", "SignUpAttempt": "sign_up_attempt", "SmsMessage": "sms_message", "Token": "token", @@ -71,12 +75,15 @@ Object { "OrganizationMembership": [Function], "OrganizationMembershipPublicUserData": [Function], "PhoneNumber": [Function], + "RedirectUrl": [Function], "SMSMessage": [Function], "Session": [Function], + "SignInToken": [Function], "Token": [Function], "User": [Function], "Verification": [Function], "createGetToken": [Function], "createSignedOutState": [Function], + "deserialize": [Function], } `; diff --git a/packages/backend-core/src/__tests__/apis/AllowlistIdentifierApi.test.ts b/packages/backend-core/src/__tests__/endpoints/AllowlistIdentifierApi.test.ts similarity index 70% rename from packages/backend-core/src/__tests__/apis/AllowlistIdentifierApi.test.ts rename to packages/backend-core/src/__tests__/endpoints/AllowlistIdentifierApi.test.ts index a0d53990c9c..20febf92809 100644 --- a/packages/backend-core/src/__tests__/apis/AllowlistIdentifierApi.test.ts +++ b/packages/backend-core/src/__tests__/endpoints/AllowlistIdentifierApi.test.ts @@ -1,14 +1,18 @@ import nock from 'nock'; import { AllowlistIdentifier } from '../../api/resources'; -import { TestBackendAPIClient } from '../TestBackendAPI'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +afterEach(() => { + nock.cleanAll(); +}); test('getAllowlistIdentifierList() returns a list of allowlist identifiers', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get('/v1/allowlist_identifiers') .replyWithFile(200, __dirname + '/responses/getAllowlistIdentifierList.json', {}); - const allowlistIdentifierList = await TestBackendAPIClient.allowlistIdentifiers.getAllowlistIdentifierList(); + const allowlistIdentifierList = await TestClerkAPI.allowlistIdentifiers.getAllowlistIdentifierList(); expect(allowlistIdentifierList).toBeInstanceOf(Array); expect(allowlistIdentifierList.length).toEqual(1); expect(allowlistIdentifierList[0]).toBeInstanceOf(AllowlistIdentifier); @@ -24,7 +28,7 @@ test('createAllowlistIdentifier() creates an allowlist identifier', async () => updated_at: 1611948436, }; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post( '/v1/allowlist_identifiers', JSON.stringify({ @@ -34,7 +38,7 @@ test('createAllowlistIdentifier() creates an allowlist identifier', async () => ) .reply(200, resJSON); - const allowlistIdentifier = await TestBackendAPIClient.allowlistIdentifiers.createAllowlistIdentifier({ + const allowlistIdentifier = await TestClerkAPI.allowlistIdentifiers.createAllowlistIdentifier({ identifier, notify: false, }); @@ -53,16 +57,16 @@ test('deleteAllowlistIdentifier() deletes an allowlist identifier', async () => updated_at: 1611948436, }; - nock('https://api.clerk.dev').delete(`/v1/allowlist_identifiers/${id}`).reply(200, resJSON); + nock(defaultServerAPIUrl).delete(`/v1/allowlist_identifiers/${id}`).reply(200, resJSON); - const allowlistIdentifier = await TestBackendAPIClient.allowlistIdentifiers.deleteAllowlistIdentifier(id); + const allowlistIdentifier = await TestClerkAPI.allowlistIdentifiers.deleteAllowlistIdentifier(id); expect(allowlistIdentifier).toEqual( new AllowlistIdentifier(id, resJSON.identifier, resJSON.created_at, resJSON.updated_at), ); }); test('deleteAllowlistIdentifier() throws an error without allowlist identifier ID', async () => { - await expect(TestBackendAPIClient.allowlistIdentifiers.deleteAllowlistIdentifier('')).rejects.toThrow( - 'A valid ID is required.', + await expect(TestClerkAPI.allowlistIdentifiers.deleteAllowlistIdentifier('')).rejects.toThrow( + 'A valid resource ID is required.', ); }); diff --git a/packages/backend-core/src/__tests__/apis/ClientApi.test.ts b/packages/backend-core/src/__tests__/endpoints/ClientApi.test.ts similarity index 81% rename from packages/backend-core/src/__tests__/apis/ClientApi.test.ts rename to packages/backend-core/src/__tests__/endpoints/ClientApi.test.ts index b9f42a68881..c28dbe4ab27 100644 --- a/packages/backend-core/src/__tests__/apis/ClientApi.test.ts +++ b/packages/backend-core/src/__tests__/endpoints/ClientApi.test.ts @@ -1,16 +1,20 @@ import nock from 'nock'; import { Client } from '../../api/resources'; -import { TestBackendAPIClient } from '../TestBackendAPI'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +afterEach(() => { + nock.cleanAll(); +}); test('getClientList() returns a list of clients', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get('/v1/clients') .replyWithFile(200, __dirname + '/responses/getClientList.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const clientList = await TestBackendAPIClient.clients.getClientList(); + const clientList = await TestClerkAPI.clients.getClientList(); expect(clientList).toBeInstanceOf(Array); expect(clientList.length).toEqual(2); @@ -95,19 +99,19 @@ test('getClient() returns a single client', async () => { 1630846634, ); - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get(`/v1/clients/${expected.id}`) .replyWithFile(200, __dirname + '/responses/getClient.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const client = await TestBackendAPIClient.clients.getClient(expected.id); + const client = await TestClerkAPI.clients.getClient(expected.id); expect(client).toEqual(expected); }); test('getClient() throws an error without client ID', async () => { - await expect(TestBackendAPIClient.clients.getClient('')).rejects.toThrow('A valid ID is required.'); + await expect(TestClerkAPI.clients.getClient('')).rejects.toThrow('A valid resource ID is required.'); }); test('verifyClient() returns a client if verified', async () => { @@ -138,13 +142,13 @@ test('verifyClient() returns a client if verified', async () => { const sessionToken = 'random_jwt_token'; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post(`/v1/clients/verify`, { token: sessionToken }) .replyWithFile(200, __dirname + '/responses/getClient.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const client = await TestBackendAPIClient.clients.verifyClient(sessionToken); + const client = await TestClerkAPI.clients.verifyClient(sessionToken); expect(client).toEqual(expected); }); diff --git a/packages/backend-core/src/__tests__/apis/EmailApi.test.ts b/packages/backend-core/src/__tests__/endpoints/EmailApi.test.ts similarity index 78% rename from packages/backend-core/src/__tests__/apis/EmailApi.test.ts rename to packages/backend-core/src/__tests__/endpoints/EmailApi.test.ts index 162c658594f..561dfcc2cb5 100644 --- a/packages/backend-core/src/__tests__/apis/EmailApi.test.ts +++ b/packages/backend-core/src/__tests__/endpoints/EmailApi.test.ts @@ -2,7 +2,11 @@ import nock from 'nock'; import snakecaseKeys from 'snakecase-keys'; import { Email } from '../../api/resources'; -import { TestBackendAPIClient } from '../TestBackendAPI'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +afterEach(() => { + nock.cleanAll(); +}); test('createEmail() sends an email', async () => { const fromEmailName = 'accounting'; @@ -10,7 +14,7 @@ test('createEmail() sends an email', async () => { const subject = 'Your account is in good standing!'; const body = 'Click here to see your most recent transactions.'; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post( '/v1/emails', snakecaseKeys({ @@ -21,10 +25,10 @@ test('createEmail() sends an email', async () => { }), ) .replyWithFile(200, __dirname + '/responses/createEmail.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const email = await TestBackendAPIClient.emails.createEmail({ + const email = await TestClerkAPI.emails.createEmail({ fromEmailName, emailAddressId, subject, diff --git a/packages/backend-core/src/__tests__/endpoints/Error.test.ts b/packages/backend-core/src/__tests__/endpoints/Error.test.ts new file mode 100644 index 00000000000..22df37cfbf3 --- /dev/null +++ b/packages/backend-core/src/__tests__/endpoints/Error.test.ts @@ -0,0 +1,35 @@ +import nock from 'nock'; + +import { ClerkAPIResponseError } from '../../api/errors'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +const respJSON = { + errors: [ + { + message: 'Boom!', + long_message: 'Boom! Something went wrong.', + code: 'API_error', + meta: { + param_name: 'whatever', + }, + }, + ], +}; + +afterEach(() => { + nock.cleanAll(); +}); + +test('parses error response', async () => { + nock(defaultServerAPIUrl).get('/v1/users').reply(400, respJSON); + try { + await TestClerkAPI.users.getUserList(); + } catch (err) { + const error = err as ClerkAPIResponseError; + + expect(error.clerkError).toBeTruthy(); + expect(error.status).toBe(400); + expect(error.message).toBe('Bad Request'); + expect(error.errors[0].code).toBe('API_error'); + } +}); diff --git a/packages/backend-core/src/__tests__/apis/InvitationApi.test.ts b/packages/backend-core/src/__tests__/endpoints/InvitationApi.test.ts similarity index 78% rename from packages/backend-core/src/__tests__/apis/InvitationApi.test.ts rename to packages/backend-core/src/__tests__/endpoints/InvitationApi.test.ts index 39aba1e1677..0998e95c45c 100644 --- a/packages/backend-core/src/__tests__/apis/InvitationApi.test.ts +++ b/packages/backend-core/src/__tests__/endpoints/InvitationApi.test.ts @@ -1,14 +1,18 @@ import nock from 'nock'; import { Invitation, InvitationJSON, ObjectType } from '../../api/resources'; -import { TestBackendAPIClient } from '../TestBackendAPI'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +afterEach(() => { + nock.cleanAll(); +}); test('getInvitationList() returns a list of invitations', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get('/v1/invitations') .replyWithFile(200, __dirname + '/responses/getInvitationList.json', {}); - const invitationList = await TestBackendAPIClient.invitations.getInvitationList(); + const invitationList = await TestClerkAPI.invitations.getInvitationList(); expect(invitationList).toBeInstanceOf(Array); expect(invitationList.length).toEqual(1); expect(invitationList[0]).toBeInstanceOf(Invitation); @@ -25,13 +29,13 @@ test('createInvitation() creates an invitation', async () => { public_metadata: null, }; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post('/v1/invitations', { email_address: emailAddress, }) .reply(200, resJSON); - const invitation = await TestBackendAPIClient.invitations.createInvitation({ + const invitation = await TestClerkAPI.invitations.createInvitation({ emailAddress, }); expect(invitation).toEqual(new Invitation(resJSON.id, emailAddress, null, resJSON.created_at, resJSON.updated_at)); @@ -49,14 +53,14 @@ test('createInvitation() accepts a redirectUrl', async () => { }; const redirectUrl = 'http://redirect.org'; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post('/v1/invitations', { email_address: emailAddress, redirect_url: redirectUrl, }) .reply(200, resJSON); - const invitation = await TestBackendAPIClient.invitations.createInvitation({ + const invitation = await TestClerkAPI.invitations.createInvitation({ emailAddress, redirectUrl, }); @@ -77,14 +81,14 @@ test('createInvitation() accepts publicMetadata', async () => { updated_at: 1611948436, }; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post('/v1/invitations', { email_address: emailAddress, public_metadata: JSON.stringify(publicMetadata), }) .reply(200, resJSON); - const invitation = await TestBackendAPIClient.invitations.createInvitation({ + const invitation = await TestClerkAPI.invitations.createInvitation({ emailAddress, publicMetadata, }); @@ -104,12 +108,12 @@ test('revokeInvitation() revokes an invitation', async () => { updated_at: 1611948436, }; - nock('https://api.clerk.dev').post(`/v1/invitations/${id}/revoke`).reply(200, resJSON); + nock(defaultServerAPIUrl).post(`/v1/invitations/${id}/revoke`).reply(200, resJSON); - const invitation = await TestBackendAPIClient.invitations.revokeInvitation(id); + const invitation = await TestClerkAPI.invitations.revokeInvitation(id); expect(invitation).toEqual(new Invitation(id, resJSON.email_address, null, resJSON.created_at, resJSON.updated_at)); }); test('revokeInvitation() throws an error without invitation ID', async () => { - await expect(TestBackendAPIClient.invitations.revokeInvitation('')).rejects.toThrow('A valid ID is required.'); + await expect(TestClerkAPI.invitations.revokeInvitation('')).rejects.toThrow('A valid resource ID is required.'); }); diff --git a/packages/backend-core/src/__tests__/apis/OrganizationApi.test.ts b/packages/backend-core/src/__tests__/endpoints/OrganizationApi.test.ts similarity index 77% rename from packages/backend-core/src/__tests__/apis/OrganizationApi.test.ts rename to packages/backend-core/src/__tests__/endpoints/OrganizationApi.test.ts index 9aacde58f8e..70e74a2f9d4 100644 --- a/packages/backend-core/src/__tests__/apis/OrganizationApi.test.ts +++ b/packages/backend-core/src/__tests__/endpoints/OrganizationApi.test.ts @@ -11,7 +11,11 @@ import { OrganizationMembershipPublicUserData, } from '../../api/resources'; import { OrganizationInvitationStatus, OrganizationMembershipRole } from '../../api/resources/Enums'; -import { TestBackendAPIClient } from '../TestBackendAPI'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +afterEach(() => { + nock.cleanAll(); +}); test('getOrganizationList() retrieves a list of organizations', async () => { const resJSON = { @@ -28,9 +32,9 @@ test('getOrganizationList() retrieves a list of organizations', async () => { }, ], }; - nock('https://api.clerk.dev').get('/v1/organizations?').reply(200, resJSON); + nock(defaultServerAPIUrl).get('/v1/organizations?').reply(200, resJSON); - const organizationList = await TestBackendAPIClient.organizations.getOrganizationList(); + const organizationList = await TestClerkAPI.organizations.getOrganizationList(); expect(organizationList).toBeInstanceOf(Array); expect(organizationList.length).toEqual(1); expect(organizationList[0]).toBeInstanceOf(Organization); @@ -54,17 +58,17 @@ test('createOrganization() creates an organization', async () => { updated_at: 1611948436, }; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post('/v1/organizations', { name, slug, - public_metadata: JSON.stringify(publicMetadata), - private_metadata: JSON.stringify(privateMetadata), + public_metadata: publicMetadata, + private_metadata: privateMetadata, created_by: createdBy, }) .reply(200, resJSON); - const organization = await TestBackendAPIClient.organizations.createOrganization({ + const organization = await TestClerkAPI.organizations.createOrganization({ name, slug, createdBy, @@ -89,13 +93,13 @@ test('getOrganization() fetches an organization', async () => { updated_at: 1611948436, }; - nock('https://api.clerk.dev').get(`/v1/organizations/${id}`).reply(200, resJSON); + nock(defaultServerAPIUrl).get(`/v1/organizations/${id}`).reply(200, resJSON); - let organization = await TestBackendAPIClient.organizations.getOrganization({ organizationId: id }); + let organization = await TestClerkAPI.organizations.getOrganization({ organizationId: id }); expect(organization).toEqual(Organization.fromJSON(resJSON)); - nock('https://api.clerk.dev').get(`/v1/organizations/${slug}`).reply(200, resJSON); - organization = await TestBackendAPIClient.organizations.getOrganization({ slug }); + nock(defaultServerAPIUrl).get(`/v1/organizations/${slug}`).reply(200, resJSON); + organization = await TestClerkAPI.organizations.getOrganization({ slug }); expect(organization).toEqual(Organization.fromJSON(resJSON)); }); @@ -114,9 +118,9 @@ test('updateOrganization() updates organization', async () => { public_metadata: {}, }; - nock('https://api.clerk.dev').patch(`/v1/organizations/${id}`, { name }).reply(200, resJSON); + nock(defaultServerAPIUrl).patch(`/v1/organizations/${id}`, { name }).reply(200, resJSON); - const organization = await TestBackendAPIClient.organizations.updateOrganization(id, { name }); + const organization = await TestClerkAPI.organizations.updateOrganization(id, { name }); expect(organization).toEqual(Organization.fromJSON(resJSON)); }); @@ -137,14 +141,14 @@ test('updateOrganizationMetadata() updates organization metadata', async () => { logo_url: null, }; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .patch(`/v1/organizations/${id}/metadata`, { - public_metadata: JSON.stringify(publicMetadata), - private_metadata: JSON.stringify(privateMetadata), + public_metadata: publicMetadata, + private_metadata: privateMetadata, }) .reply(200, resJSON); - const organization = await TestBackendAPIClient.organizations.updateOrganizationMetadata(id, { + const organization = await TestClerkAPI.organizations.updateOrganizationMetadata(id, { publicMetadata, privateMetadata, }); @@ -153,8 +157,8 @@ test('updateOrganizationMetadata() updates organization metadata', async () => { test('deleteOrganization() deletes organization', async () => { const id = 'org_randomid'; - nock('https://api.clerk.dev').delete(`/v1/organizations/${id}`).reply(200, {}); - await TestBackendAPIClient.organizations.deleteOrganization(id); + nock(defaultServerAPIUrl).delete(`/v1/organizations/${id}`).reply(200, {}); + await TestClerkAPI.organizations.deleteOrganization(id); }); test('getOrganizationMembershipList() returns a list of organization memberships', async () => { @@ -184,11 +188,11 @@ test('getOrganizationMembershipList() returns a list of organization memberships }, ]; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get(new RegExp(`/v1/organizations/${organizationId}/memberships`)) .reply(200, resJSON); - const organizationMembershipList = await TestBackendAPIClient.organizations.getOrganizationMembershipList({ + const organizationMembershipList = await TestClerkAPI.organizations.getOrganizationMembershipList({ organizationId, }); expect(organizationMembershipList).toBeInstanceOf(Array); @@ -228,9 +232,9 @@ test('createOrganizationMembership() creates a membership for an organization', updated_at: 1612378465, }; - nock('https://api.clerk.dev').post(`/v1/organizations/${organizationId}/memberships`).reply(200, resJSON); + nock(defaultServerAPIUrl).post(`/v1/organizations/${organizationId}/memberships`).reply(200, resJSON); - const orgMembership = await TestBackendAPIClient.organizations.createOrganizationMembership({ + const orgMembership = await TestClerkAPI.organizations.createOrganizationMembership({ organizationId, userId, role, @@ -268,9 +272,9 @@ test('updateOrganizationMembership() updates an organization membership', async updated_at: 1612378465, }; - nock('https://api.clerk.dev').patch(`/v1/organizations/${organizationId}/memberships/${userId}`).reply(200, resJSON); + nock(defaultServerAPIUrl).patch(`/v1/organizations/${organizationId}/memberships/${userId}`).reply(200, resJSON); - const orgMembership = await TestBackendAPIClient.organizations.updateOrganizationMembership({ + const orgMembership = await TestClerkAPI.organizations.updateOrganizationMembership({ organizationId, userId, role, @@ -281,8 +285,8 @@ test('updateOrganizationMembership() updates an organization membership', async test('deleteOrganizationMembership() deletes an organization', async () => { const organizationId = 'org_randomid'; const userId = 'user_randomid'; - nock('https://api.clerk.dev').delete(`/v1/organizations/${organizationId}/memberships/${userId}`).reply(200, {}); - await TestBackendAPIClient.organizations.deleteOrganizationMembership({ organizationId, userId }); + nock(defaultServerAPIUrl).delete(`/v1/organizations/${organizationId}/memberships/${userId}`).reply(200, {}); + await TestClerkAPI.organizations.deleteOrganizationMembership({ organizationId, userId }); }); test('createOrganizationInvitation() creates an invitation for an organization', async () => { @@ -302,9 +306,9 @@ test('createOrganizationInvitation() creates an invitation for an organization', updated_at: 1612378465, }; - nock('https://api.clerk.dev').post(`/v1/organizations/${organizationId}/invitations`).reply(200, resJSON); + nock(defaultServerAPIUrl).post(`/v1/organizations/${organizationId}/invitations`).reply(200, resJSON); - const orgInvitation = await TestBackendAPIClient.organizations.createOrganizationInvitation({ + const orgInvitation = await TestClerkAPI.organizations.createOrganizationInvitation({ organizationId, emailAddress, role, @@ -329,11 +333,11 @@ test('getPendingOrganizationInvitationList() returns a list of organization memb }, ]; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get(new RegExp(`/v1/organizations/${organizationId}/invitations/pending`)) .reply(200, resJSON); - const organizationInvitationList = await TestBackendAPIClient.organizations.getPendingOrganizationInvitationList({ + const organizationInvitationList = await TestClerkAPI.organizations.getPendingOrganizationInvitationList({ organizationId, }); expect(organizationInvitationList).toBeInstanceOf(Array); @@ -354,11 +358,11 @@ test('revokeOrganizationInvitation() revokes an organization invitation', async created_at: 1612378465, updated_at: 1612378465, }; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post(`/v1/organizations/${organizationId}/invitations/${invitationId}/revoke`) .reply(200, resJSON); - const orgInvitation = await TestBackendAPIClient.organizations.revokeOrganizationInvitation({ + const orgInvitation = await TestClerkAPI.organizations.revokeOrganizationInvitation({ organizationId, invitationId, requestingUserId: 'user_randomid', diff --git a/packages/backend-core/src/__tests__/endpoints/RedirectUrl.test.ts b/packages/backend-core/src/__tests__/endpoints/RedirectUrl.test.ts new file mode 100644 index 00000000000..6d4e5e5aa9a --- /dev/null +++ b/packages/backend-core/src/__tests__/endpoints/RedirectUrl.test.ts @@ -0,0 +1,62 @@ +import nock from 'nock'; + +import { RedirectUrl } from '../../api/resources'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +const resJSON = { + object: 'redirect_url', + id: 'ru_28eW1GeqywLZSzoBoaHJ79pkPR6', + url: 'my-app://oauth-callback', + created_at: 1651577314987, + updated_at: 1651577314987, +}; + +afterEach(() => { + nock.cleanAll(); +}); + +test('getRedirectUrlList() returns a list of redirect urls', async () => { + nock(defaultServerAPIUrl).get('/v1/redirect_urls').reply(200, [resJSON]); + + const redirectUrlsList = await TestClerkAPI.redirectUrls.getRedirectUrlList(); + expect(redirectUrlsList).toBeInstanceOf(Array); + expect(redirectUrlsList.length).toEqual(1); + expect(redirectUrlsList[0]).toBeInstanceOf(RedirectUrl); +}); + +test('getRedirectUrl() returns a redirect url', async () => { + nock(defaultServerAPIUrl).get(`/v1/redirect_urls/${resJSON.id}`).reply(200, resJSON); + + const redirectUrl = await TestClerkAPI.redirectUrls.getRedirectUrl(resJSON.id); + expect(redirectUrl).toBeInstanceOf(RedirectUrl); + expect(redirectUrl.url).toBe(resJSON.url); +}); + +test('getRedirectUrl() throws an error without redirect url ID', async () => { + await expect(TestClerkAPI.redirectUrls.getRedirectUrl('')).rejects.toThrow('A valid resource ID is required.'); +}); + +test('createRedirectUrl() creates a new redirect url', async () => { + nock(defaultServerAPIUrl) + .post( + '/v1/redirect_urls', + JSON.stringify({ + url: resJSON.url, + }), + ) + .reply(200, resJSON); + + const redirectUrl = await TestClerkAPI.redirectUrls.createRedirectUrl({ + url: resJSON.url, + }); + expect(redirectUrl).toEqual(new RedirectUrl(resJSON.id, resJSON.url, resJSON.created_at, resJSON.updated_at)); +}); + +test('deleteRedirectUrl() deletes an redirect url', async () => { + nock(defaultServerAPIUrl).delete(`/v1/redirect_urls/${resJSON.id}`).reply(200, {}); + await TestClerkAPI.redirectUrls.deleteRedirectUrl(resJSON.id); +}); + +test('deleteRedirectUrl() throws an error without redirect url ID', async () => { + await expect(TestClerkAPI.redirectUrls.deleteRedirectUrl('')).rejects.toThrow('A valid resource ID is required.'); +}); diff --git a/packages/backend-core/src/__tests__/apis/SMSMessageApi.test.ts b/packages/backend-core/src/__tests__/endpoints/SMSMessageApi.test.ts similarity index 72% rename from packages/backend-core/src/__tests__/apis/SMSMessageApi.test.ts rename to packages/backend-core/src/__tests__/endpoints/SMSMessageApi.test.ts index 969da9aad48..af1c61bfef0 100644 --- a/packages/backend-core/src/__tests__/apis/SMSMessageApi.test.ts +++ b/packages/backend-core/src/__tests__/endpoints/SMSMessageApi.test.ts @@ -2,19 +2,23 @@ import nock from 'nock'; import snakecaseKeys from 'snakecase-keys'; import { SMSMessage } from '../../api/resources'; -import { TestBackendAPIClient } from '../TestBackendAPI'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +afterEach(() => { + nock.cleanAll(); +}); test('createSMSMessage() sends an SMS message', async () => { const phoneNumberId = 'idn_random'; const message = 'Press F to pay pespects'; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post('/v1/sms_messages', snakecaseKeys({ phoneNumberId, message })) .replyWithFile(200, __dirname + '/responses/createSMSMessage.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const smsMessage = await TestBackendAPIClient.smsMessages.createSMSMessage({ + const smsMessage = await TestClerkAPI.smsMessages.createSMSMessage({ phoneNumberId, message, }); diff --git a/packages/backend-core/src/__tests__/apis/SessionApi.test.ts b/packages/backend-core/src/__tests__/endpoints/SessionApi.test.ts similarity index 68% rename from packages/backend-core/src/__tests__/apis/SessionApi.test.ts rename to packages/backend-core/src/__tests__/endpoints/SessionApi.test.ts index 2ba1bae2bf7..af0ab72a0ab 100644 --- a/packages/backend-core/src/__tests__/apis/SessionApi.test.ts +++ b/packages/backend-core/src/__tests__/endpoints/SessionApi.test.ts @@ -1,16 +1,20 @@ import nock from 'nock'; import { Session } from '../../api/resources'; -import { TestBackendAPIClient } from '../TestBackendAPI'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +afterEach(() => { + nock.cleanAll(); +}); test('getSessionList() returns a list of sessions', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get('/v1/sessions') .replyWithFile(200, __dirname + '/responses/getSessionList.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const sessionList = await TestBackendAPIClient.sessions.getSessionList(); + const sessionList = await TestClerkAPI.sessions.getSessionList(); expect(sessionList).toBeInstanceOf(Array); expect(sessionList.length).toEqual(2); @@ -56,23 +60,23 @@ test('getSession() returns a single session', async () => { 1613593533, ); - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get(`/v1/sessions/${expected.id}`) .replyWithFile(200, __dirname + '/responses/getSession.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const session = await TestBackendAPIClient.sessions.getSession(expected.id); + const session = await TestClerkAPI.sessions.getSession(expected.id); expect(session).toEqual(expected); }); test('getSession() throws an error without session ID', async () => { - await expect(TestBackendAPIClient.sessions.getSession('')).rejects.toThrow('A valid ID is required.'); + await expect(TestClerkAPI.sessions.getSession('')).rejects.toThrow('A valid resource ID is required.'); }); test('revokeSession() throws an error without session ID', async () => { - await expect(TestBackendAPIClient.sessions.revokeSession('')).rejects.toThrow('A valid ID is required.'); + await expect(TestClerkAPI.sessions.revokeSession('')).rejects.toThrow('A valid resource ID is required.'); }); test('verifySession() returns a session if verified', async () => { @@ -90,17 +94,17 @@ test('verifySession() returns a session if verified', async () => { const sessionToken = 'random_jwt_token'; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post(`/v1/sessions/${expected.id}/verify`, { token: sessionToken }) .replyWithFile(200, __dirname + '/responses/getSession.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const session = await TestBackendAPIClient.sessions.verifySession(expected.id, sessionToken); + const session = await TestClerkAPI.sessions.verifySession(expected.id, sessionToken); expect(session).toEqual(expected); }); test('verifySession() throws an error without session ID', async () => { - await expect(TestBackendAPIClient.sessions.verifySession('', '')).rejects.toThrow('A valid ID is required.'); + await expect(TestClerkAPI.sessions.verifySession('', '')).rejects.toThrow('A valid resource ID is required.'); }); diff --git a/packages/backend-core/src/__tests__/endpoints/SignInToken.test.ts b/packages/backend-core/src/__tests__/endpoints/SignInToken.test.ts new file mode 100644 index 00000000000..4381d0423f2 --- /dev/null +++ b/packages/backend-core/src/__tests__/endpoints/SignInToken.test.ts @@ -0,0 +1,39 @@ +import nock from 'nock'; + +import { SignInToken } from '../../api/resources'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +const resJSON = { + object: 'sign_in_token', + id: 'sit_26Ed5ZqqJcOjRwecRQij2ZovDdG', + user_id: 'user_26Ect5GuCCeaFWwSDiiKcgAGtVk', + token: + 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJFeHBpcmVzSW5TZWNvbmRzIjo1LCJleHAiOjE2NDY5OTI1MDEsImlpZCI6Imluc18yNkVja3R0TnJDamE3YTZQT0xINTVDQVBpZmQiLCJzaWQiOiJzaXRfMjZFZDVacXFKY09qUndlY1JRaWoyWm92RGRHIiwic3QiOiJzaWduX2luX3Rva2VuIn0.j6Gwl6g2QcAJ9AjRvG1k7aUrnMCyPU49hYgTlmDG9gD_8Yd7sxUepyDdCHRaDaABlWg-G3tUs09HRfdrAXM-4e6NwcEy_ak1LWkE3G6WVhPnlomwH7n7BsIbmoybf91Eel0XRlb33XdUVaWNaA_CH8INkVLtXfZWTorNsAN2-Es_6G-Jtz4Zvw8hZBtXQDMSlyl27rxohMvfefv-ffG6Kd0XsvT9yYj2kik5KcONMWO6XEPtMZRoHzMabnmPQbLrUPBmbnU_1UVFpxL0LfuOXlxbV3LIvuejmhNZZtR0ZwcbrAnXruof4KjmCK_QOpqShI3dTlyYTV18amy2se5oxA', + status: 'pending', + created_at: 1638000669544, + updated_at: 1638000669544, +}; + +afterEach(() => { + nock.cleanAll(); +}); + +test('createSignInToken() creates a new sign in token', async () => { + nock(defaultServerAPIUrl).post('/v1/sign_in_tokens').reply(200, resJSON); + + const signInToken = await TestClerkAPI.signInTokens.createSignInToken({ + userId: resJSON.user_id, + expiresInSeconds: 60, + }); + expect(signInToken).toBeInstanceOf(SignInToken); + expect(signInToken.userId).toBe(resJSON.user_id); +}); + +test('revokeSignInToken() revokes a sign in token', async () => { + nock(defaultServerAPIUrl).post(`/v1/sign_in_tokens/${resJSON.id}/revoke`).reply(200, resJSON); + await TestClerkAPI.signInTokens.revokeSignInToken(resJSON.id); +}); + +test('revokeSignInToken() throws an error without sign in tokens url ID', async () => { + await expect(TestClerkAPI.signInTokens.revokeSignInToken('')).rejects.toThrow('A valid resource ID is required.'); +}); diff --git a/packages/backend-core/src/__tests__/apis/UserApi.test.ts b/packages/backend-core/src/__tests__/endpoints/UserApi.test.ts similarity index 72% rename from packages/backend-core/src/__tests__/apis/UserApi.test.ts rename to packages/backend-core/src/__tests__/endpoints/UserApi.test.ts index be83bd3e49b..105f87abf43 100644 --- a/packages/backend-core/src/__tests__/apis/UserApi.test.ts +++ b/packages/backend-core/src/__tests__/endpoints/UserApi.test.ts @@ -2,16 +2,20 @@ import nock from 'nock'; import snakecaseKeys from 'snakecase-keys'; import { User } from '../../api/resources'; -import { TestBackendAPIClient } from '../TestBackendAPI'; +import { defaultServerAPIUrl, TestClerkAPI } from '../TestClerkApi'; + +afterEach(() => { + nock.cleanAll(); +}); test('getUserList() returns a list of users', async () => { - nock('https://api.clerk.dev') - .get(new RegExp(/v1\/users?\?/)) + nock(defaultServerAPIUrl) + .get('/v1/users') .replyWithFile(200, __dirname + '/responses/getUserList.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const userList = await TestBackendAPIClient.users.getUserList(); + const userList = await TestClerkAPI.users.getUserList(); expect(userList).toBeInstanceOf(Array); expect(userList.length).toEqual(1); @@ -19,13 +23,13 @@ test('getUserList() returns a list of users', async () => { }); test('getUserList() with limit returns a list of users', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get('/v1/users?limit=1') .replyWithFile(200, __dirname + '/responses/getUserList.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const userList = await TestBackendAPIClient.users.getUserList({ limit: 1 }); + const userList = await TestClerkAPI.users.getUserList({ limit: 1 }); expect(userList).toBeInstanceOf(Array); expect(userList.length).toEqual(1); @@ -33,13 +37,13 @@ test('getUserList() with limit returns a list of users', async () => { }); test('getUserList() with limit and offset returns a list of users', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get('/v1/users?limit=1&offset=1') .replyWithFile(200, __dirname + '/responses/getUserList.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const userList = await TestBackendAPIClient.users.getUserList({ + const userList = await TestClerkAPI.users.getUserList({ limit: 1, offset: 1, }); @@ -50,13 +54,13 @@ test('getUserList() with limit and offset returns a list of users', async () => }); test('getUserList() with ordering returns a list of users', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get('/v1/users?order_by=%2Bupdated_at') .replyWithFile(200, __dirname + '/responses/getUserList.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const userList = await TestBackendAPIClient.users.getUserList({ + const userList = await TestClerkAPI.users.getUserList({ orderBy: '+updated_at', }); @@ -66,13 +70,13 @@ test('getUserList() with ordering returns a list of users', async () => { }); test('getUserList() with emails returns a list of users', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get('/v1/users?email_address=email1&email_address=email2') .replyWithFile(200, __dirname + '/responses/getUserList.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const userList = await TestBackendAPIClient.users.getUserList({ + const userList = await TestClerkAPI.users.getUserList({ emailAddress: ['email1', 'email2'], }); @@ -82,13 +86,13 @@ test('getUserList() with emails returns a list of users', async () => { }); test('getUserList() with phone numbers returns a list of users', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get('/v1/users?phone_number=phone1&phone_number=phone2') .replyWithFile(200, __dirname + '/responses/getUserList.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const userList = await TestBackendAPIClient.users.getUserList({ + const userList = await TestClerkAPI.users.getUserList({ phoneNumber: ['phone1', 'phone2'], }); @@ -98,13 +102,13 @@ test('getUserList() with phone numbers returns a list of users', async () => { }); test('getUser() returns a single user', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get(`/v1/users/user_deadbeef`) .replyWithFile(200, __dirname + '/responses/getUser.json', { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', }); - const user = await TestBackendAPIClient.users.getUser('user_deadbeef'); + const user = await TestClerkAPI.users.getUser('user_deadbeef'); expect(user).toBeInstanceOf(User); @@ -131,7 +135,7 @@ test('getUser() returns a single user', async () => { }); test('getUser() throws an error without user ID', async () => { - await expect(TestBackendAPIClient.users.getUser('')).rejects.toThrow('A valid ID is required.'); + await expect(TestClerkAPI.users.getUser('')).rejects.toThrow('A valid resource ID is required.'); }); test('createUser() creates a user', async () => { @@ -143,13 +147,13 @@ test('createUser() creates a user', async () => { lastName: 'Clerk', }; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .post('/v1/users', snakecaseKeys(params)) .replyWithFile(200, __dirname + '/responses/createUser.json', { 'Content-Type': '', }); - const user = await TestBackendAPIClient.users.createUser(params); + const user = await TestClerkAPI.users.createUser(params); expect(user.firstName).toEqual('Boss'); }); @@ -163,30 +167,30 @@ test('updateUser() updates a user', async () => { lastName: 'Clerk', }; - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .patch('/v1/users/user_1oBNj55jOjSK9rOYrT5QHqj7eaK', snakecaseKeys(params)) .replyWithFile(200, __dirname + '/responses/updateUser.json', { 'Content-Type': '', }); - const user = await TestBackendAPIClient.users.updateUser('user_1oBNj55jOjSK9rOYrT5QHqj7eaK', params); + const user = await TestClerkAPI.users.updateUser('user_1oBNj55jOjSK9rOYrT5QHqj7eaK', params); expect(user.firstName).toEqual('Boss'); expect(user.username).toEqual('clerk_boss'); }); test('updateUser() throws an error without user ID', async () => { - await expect(TestBackendAPIClient.users.updateUser('', {})).rejects.toThrow('A valid ID is required.'); + await expect(TestClerkAPI.users.updateUser('', {})).rejects.toThrow('A valid resource ID is required.'); }); test('deleteUser() throws an error without user ID', async () => { - await expect(TestBackendAPIClient.users.deleteUser('')).rejects.toThrow('A valid ID is required.'); + await expect(TestClerkAPI.users.deleteUser('')).rejects.toThrow('A valid resource ID is required.'); }); test('getCount() returns a valid number response', async () => { - nock('https://api.clerk.dev') + nock(defaultServerAPIUrl) .get(`/v1/users/count?`) .replyWithFile(200, __dirname + '/responses/getCount.json'); - const userCount = await TestBackendAPIClient.users.getCount(); + const userCount = await TestClerkAPI.users.getCount(); expect(userCount).toEqual(1); }); diff --git a/packages/backend-core/src/__tests__/apis/responses/createEmail.json b/packages/backend-core/src/__tests__/endpoints/responses/createEmail.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/createEmail.json rename to packages/backend-core/src/__tests__/endpoints/responses/createEmail.json diff --git a/packages/backend-core/src/__tests__/apis/responses/createSMSMessage.json b/packages/backend-core/src/__tests__/endpoints/responses/createSMSMessage.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/createSMSMessage.json rename to packages/backend-core/src/__tests__/endpoints/responses/createSMSMessage.json diff --git a/packages/backend-core/src/__tests__/apis/responses/createUser.json b/packages/backend-core/src/__tests__/endpoints/responses/createUser.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/createUser.json rename to packages/backend-core/src/__tests__/endpoints/responses/createUser.json diff --git a/packages/backend-core/src/__tests__/apis/responses/getAllowlistIdentifierList.json b/packages/backend-core/src/__tests__/endpoints/responses/getAllowlistIdentifierList.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/getAllowlistIdentifierList.json rename to packages/backend-core/src/__tests__/endpoints/responses/getAllowlistIdentifierList.json diff --git a/packages/backend-core/src/__tests__/apis/responses/getClient.json b/packages/backend-core/src/__tests__/endpoints/responses/getClient.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/getClient.json rename to packages/backend-core/src/__tests__/endpoints/responses/getClient.json diff --git a/packages/backend-core/src/__tests__/apis/responses/getClientList.json b/packages/backend-core/src/__tests__/endpoints/responses/getClientList.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/getClientList.json rename to packages/backend-core/src/__tests__/endpoints/responses/getClientList.json diff --git a/packages/backend-core/src/__tests__/apis/responses/getCount.json b/packages/backend-core/src/__tests__/endpoints/responses/getCount.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/getCount.json rename to packages/backend-core/src/__tests__/endpoints/responses/getCount.json diff --git a/packages/backend-core/src/__tests__/apis/responses/getInvitationList.json b/packages/backend-core/src/__tests__/endpoints/responses/getInvitationList.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/getInvitationList.json rename to packages/backend-core/src/__tests__/endpoints/responses/getInvitationList.json diff --git a/packages/backend-core/src/__tests__/apis/responses/getSession.json b/packages/backend-core/src/__tests__/endpoints/responses/getSession.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/getSession.json rename to packages/backend-core/src/__tests__/endpoints/responses/getSession.json diff --git a/packages/backend-core/src/__tests__/apis/responses/getSessionList.json b/packages/backend-core/src/__tests__/endpoints/responses/getSessionList.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/getSessionList.json rename to packages/backend-core/src/__tests__/endpoints/responses/getSessionList.json diff --git a/packages/backend-core/src/__tests__/apis/responses/getUser.json b/packages/backend-core/src/__tests__/endpoints/responses/getUser.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/getUser.json rename to packages/backend-core/src/__tests__/endpoints/responses/getUser.json diff --git a/packages/backend-core/src/__tests__/apis/responses/getUserList.json b/packages/backend-core/src/__tests__/endpoints/responses/getUserList.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/getUserList.json rename to packages/backend-core/src/__tests__/endpoints/responses/getUserList.json diff --git a/packages/backend-core/src/__tests__/apis/responses/updateUser.json b/packages/backend-core/src/__tests__/endpoints/responses/updateUser.json similarity index 100% rename from packages/backend-core/src/__tests__/apis/responses/updateUser.json rename to packages/backend-core/src/__tests__/endpoints/responses/updateUser.json diff --git a/packages/backend-core/src/__tests__/resources/Client.test.ts b/packages/backend-core/src/__tests__/resources/Client.test.ts deleted file mode 100644 index 0ecf8343778..00000000000 --- a/packages/backend-core/src/__tests__/resources/Client.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Client, ClientJSON, ObjectType } from '../../api/resources'; - -const mockClientDTO: ClientJSON = { - id: 'mock_client_dto', - session_ids: ['mock_session_ids'], - sessions: [ - { - id: 'mock_session', - client_id: 'mock_client_id', - user_id: 'mock_user_id', - object: ObjectType.Session, - status: 'mock_status', - last_active_at: 12345, - expire_at: 12345, - abandon_at: 12345, - created_at: 12345, - updated_at: 12345, - }, - ], - object: ObjectType.Client, - last_active_session_id: 'mock_active_session_id', - sign_in_attempt_id: 'mock_attempt_id', - sign_up_attempt_id: null, - sign_in_id: null, - sign_up_id: null, - created_at: 12345, - updated_at: 12345, -}; - -test('client defaults', function () { - const client = Client.fromJSON(mockClientDTO); - expect(client).toMatchSnapshot(); -}); diff --git a/packages/backend-core/src/__tests__/resources/EmailAddress.test.ts b/packages/backend-core/src/__tests__/resources/EmailAddress.test.ts deleted file mode 100644 index 7a1f48ea1e4..00000000000 --- a/packages/backend-core/src/__tests__/resources/EmailAddress.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { EmailAddress, EmailAddressJSON, ObjectType } from '../../api/resources'; - -const mockEmailAddressDTO: EmailAddressJSON = { - id: 'mock_email_address_id', - object: ObjectType.EmailAddress, - email_address: 'test@example.com', - verification: { - id: 'test_verification', - object: ObjectType.Email, - status: 'verified', - strategy: 'email', - attempts: 0, - external_verification_redirect_url: '', - expire_at: 12345, - nonce: '', - }, - linked_to: [], -}; - -test('email address defaults', function () { - const emailAddress = EmailAddress.fromJSON(mockEmailAddressDTO); - expect(emailAddress).toMatchSnapshot(); -}); diff --git a/packages/backend-core/src/__tests__/resources/PhoneNumber.test.ts b/packages/backend-core/src/__tests__/resources/PhoneNumber.test.ts deleted file mode 100644 index 363272a8b50..00000000000 --- a/packages/backend-core/src/__tests__/resources/PhoneNumber.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ObjectType, PhoneNumber, PhoneNumberJSON } from '../../api/resources'; - -const mockPhoneNumberDTO: PhoneNumberJSON = { - object: ObjectType.PhoneNumber, - id: 'mock_phone_number_id', - phone_number: '+12345678', - reserved_for_second_factor: true, - default_second_factor: true, - linked_to: [], - verification: null, -}; - -test('phone number defaults', function () { - const phoneNumber = PhoneNumber.fromJSON(mockPhoneNumberDTO); - expect(phoneNumber).toMatchSnapshot(); -}); diff --git a/packages/backend-core/src/__tests__/resources/User.test.ts b/packages/backend-core/src/__tests__/resources/User.test.ts deleted file mode 100644 index f8fec51d002..00000000000 --- a/packages/backend-core/src/__tests__/resources/User.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { User } from '../../api/resources'; -import { ObjectType, UserJSON } from '../../api/resources/JSON'; - -const mockUserDTO: UserJSON = { - id: 'user_27jbYRV0tbvGovC63JFqaAqZsMy', - object: ObjectType.User, - username: 'test001', - first_name: 'Test', - last_name: 'Test', - gender: '', - birthday: '', - profile_image_url: 'https://www.gravatar.com/avatar?d=mp', - primary_email_address_id: 'idn_27jbYTsvdRnRyc5pFqOeD6ZkZ81', - primary_phone_number_id: 'idn_27jbYTB1izaVaORupvdREuDqqaZ', - primary_web3_wallet_id: 'test_web3wallet', - password_enabled: true, - two_factor_enabled: false, - email_addresses: [ - { - id: 'idn_27jbYTsvdRnRyc5pFqOeD6ZkZ81', - object: ObjectType.EmailAddress, - email_address: 'test@example.com', - verification: { - id: 'test_verification', - object: ObjectType.Email, - status: 'verified', - strategy: 'email', - attempts: 0, - external_verification_redirect_url: '', - expire_at: 12345, - nonce: '', - }, - linked_to: [], - }, - ], - phone_numbers: [ - { - id: 'idn_27jbYTB1izaVaORupvdREuDqqaZ', - object: ObjectType.PhoneNumber, - phone_number: '+15555555555', - reserved_for_second_factor: false, - default_second_factor: false, - verification: { - id: 'test_verification', - object: ObjectType.PhoneNumber, - status: 'verified', - strategy: 'phone', - attempts: 0, - external_verification_redirect_url: '', - expire_at: 12345, - nonce: '', - }, - linked_to: [], - }, - ], - web3_wallets: [], - external_accounts: [], - public_metadata: { gender: 'female' }, - private_metadata: { middleName: 'Test' }, - unsafe_metadata: { unsafe: 'metadata' }, - external_id: 'a-unique-id', - last_sign_in_at: 12345, - created_at: 12345, - updated_at: 12345, -}; - -test('user defaults', function () { - const user = User.fromJSON(mockUserDTO); - expect(user).toMatchSnapshot(); -}); diff --git a/packages/backend-core/src/__tests__/resources/__snapshots__/Client.test.ts.snap b/packages/backend-core/src/__tests__/resources/__snapshots__/Client.test.ts.snap deleted file mode 100644 index af54b64b515..00000000000 --- a/packages/backend-core/src/__tests__/resources/__snapshots__/Client.test.ts.snap +++ /dev/null @@ -1,30 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`client defaults 1`] = ` -Client { - "createdAt": 12345, - "id": "mock_client_dto", - "lastActiveSessionId": "mock_active_session_id", - "sessionIds": Array [ - "mock_session_ids", - ], - "sessions": Array [ - Session { - "abandonAt": 12345, - "clientId": "mock_client_id", - "createdAt": 12345, - "expireAt": 12345, - "id": "mock_session", - "lastActiveAt": 12345, - "status": "mock_status", - "updatedAt": 12345, - "userId": "mock_user_id", - }, - ], - "signInAttemptId": "mock_attempt_id", - "signInId": null, - "signUpAttemptId": null, - "signUpId": null, - "updatedAt": 12345, -} -`; diff --git a/packages/backend-core/src/__tests__/resources/__snapshots__/EmailAddress.test.ts.snap b/packages/backend-core/src/__tests__/resources/__snapshots__/EmailAddress.test.ts.snap deleted file mode 100644 index 717f6e51b00..00000000000 --- a/packages/backend-core/src/__tests__/resources/__snapshots__/EmailAddress.test.ts.snap +++ /dev/null @@ -1,17 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`email address defaults 1`] = ` -EmailAddress { - "emailAddress": "test@example.com", - "id": "mock_email_address_id", - "linkedTo": Array [], - "verification": Verification { - "attempts": 0, - "expireAt": 12345, - "externalVerificationRedirectURL": null, - "nonce": "", - "status": "verified", - "strategy": "email", - }, -} -`; diff --git a/packages/backend-core/src/__tests__/resources/__snapshots__/PhoneNumber.test.ts.snap b/packages/backend-core/src/__tests__/resources/__snapshots__/PhoneNumber.test.ts.snap deleted file mode 100644 index d7b11c0204c..00000000000 --- a/packages/backend-core/src/__tests__/resources/__snapshots__/PhoneNumber.test.ts.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`phone number defaults 1`] = ` -PhoneNumber { - "defaultSecondFactor": true, - "id": "mock_phone_number_id", - "linkedTo": Array [], - "phoneNumber": "+12345678", - "reservedForSecondFactor": true, - "verification": null, -} -`; diff --git a/packages/backend-core/src/__tests__/resources/__snapshots__/User.test.ts.snap b/packages/backend-core/src/__tests__/resources/__snapshots__/User.test.ts.snap deleted file mode 100644 index 60115de76bb..00000000000 --- a/packages/backend-core/src/__tests__/resources/__snapshots__/User.test.ts.snap +++ /dev/null @@ -1,65 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`user defaults 1`] = ` -User { - "birthday": "", - "createdAt": 12345, - "emailAddresses": Array [ - EmailAddress { - "emailAddress": "test@example.com", - "id": "idn_27jbYTsvdRnRyc5pFqOeD6ZkZ81", - "linkedTo": Array [], - "verification": Verification { - "attempts": 0, - "expireAt": 12345, - "externalVerificationRedirectURL": null, - "nonce": "", - "status": "verified", - "strategy": "email", - }, - }, - ], - "externalAccounts": Array [], - "externalId": "a-unique-id", - "firstName": "Test", - "gender": "", - "id": "user_27jbYRV0tbvGovC63JFqaAqZsMy", - "lastName": "Test", - "lastSignInAt": 12345, - "passwordEnabled": true, - "phoneNumbers": Array [ - PhoneNumber { - "defaultSecondFactor": false, - "id": "idn_27jbYTB1izaVaORupvdREuDqqaZ", - "linkedTo": Array [], - "phoneNumber": "+15555555555", - "reservedForSecondFactor": false, - "verification": Verification { - "attempts": 0, - "expireAt": 12345, - "externalVerificationRedirectURL": null, - "nonce": "", - "status": "verified", - "strategy": "phone", - }, - }, - ], - "primaryEmailAddressId": "idn_27jbYTsvdRnRyc5pFqOeD6ZkZ81", - "primaryPhoneNumberId": "idn_27jbYTB1izaVaORupvdREuDqqaZ", - "primaryWeb3WalletId": "test_web3wallet", - "privateMetadata": Object { - "middleName": "Test", - }, - "profileImageUrl": "https://www.gravatar.com/avatar?d=mp", - "publicMetadata": Object { - "gender": "female", - }, - "twoFactorEnabled": false, - "unsafeMetadata": Object { - "unsafe": "metadata", - }, - "updatedAt": 12345, - "username": "test001", - "web3Wallets": Array [], -} -`; diff --git a/packages/backend-core/src/__tests__/utils/Deserializer.test.ts b/packages/backend-core/src/__tests__/utils/Deserializer.test.ts index 6a1d74675b5..7cfc3f85921 100644 --- a/packages/backend-core/src/__tests__/utils/Deserializer.test.ts +++ b/packages/backend-core/src/__tests__/utils/Deserializer.test.ts @@ -3,6 +3,7 @@ import { AllowlistIdentifierJSON, Client, ClientJSON, + deserialize, Email, EmailJSON, Invitation, @@ -17,7 +18,6 @@ import { SMSMessage, SMSMessageJSON, } from '../../api/resources'; -import deserialize from '../../api/utils/Deserializer'; const allowlistIdentifierJSON: AllowlistIdentifierJSON = { object: ObjectType.AllowlistIdentifier, diff --git a/packages/backend-core/src/api/ClerkBackendAPI.ts b/packages/backend-core/src/api/ClerkBackendAPI.ts deleted file mode 100644 index 7234b2023ba..00000000000 --- a/packages/backend-core/src/api/ClerkBackendAPI.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { - AllowlistIdentifierApi, - ClientApi, - EmailApi, - InvitationApi, - OrganizationApi, - SessionApi, - SMSMessageApi, - UserApi, -} from './collection'; -import RestClient, { ClerkFetcher } from './utils/RestClient'; - -const defaultApiKey = process.env.CLERK_API_KEY || ''; -const defaultApiVersion = process.env.CLERK_API_VERSION || 'v1'; -const defaultServerApiUrl = process.env.CLERK_API_URL || 'https://api.clerk.dev'; - -type ClerkBackendAPIProps = { - /* Backend API key */ - apiKey?: string; - /* Backend API URL */ - serverApiUrl?: string; - /* Backend API version */ - apiVersion?: string; - /* - * HTTP fetch implementation (not specific to window.fetch API). - * The fetcher implementation should return the response body, not the whole response. - */ - fetcher: ClerkFetcher; - /* Library/SDK name */ - libName: string; - /* Library/SDK semver version string */ - libVersion: string; - /* Library/SDK repository URL */ - packageRepo: string; -}; - -export class ClerkBackendAPI { - private _restClient: RestClient; - // singleton instance - static _instance: ClerkBackendAPI; - - // TODO we may not need to instantiate these any more if they keep no state - // private api instances - private _allowlistIdentifierApi?: AllowlistIdentifierApi; - private _clientApi?: ClientApi; - private _emailApi?: EmailApi; - private _invitationApi?: InvitationApi; - private _organizationApi?: OrganizationApi; - private _sessionApi?: SessionApi; - private _smsMessageApi?: SMSMessageApi; - private _userApi?: UserApi; - - constructor({ - apiKey = defaultApiKey, - serverApiUrl = defaultServerApiUrl, - apiVersion = defaultApiVersion, - fetcher, - libName, - libVersion, - packageRepo, - }: ClerkBackendAPIProps) { - // if (!apiKey) { - // throw Error(SupportMessages.API_KEY_NOT_FOUND); - // } - - this._restClient = new RestClient(apiKey, serverApiUrl, apiVersion, fetcher, libName, libVersion, packageRepo); - } - - fetchInterstitial(): Promise { - return this._restClient.fetchInterstitial(); - } - - // // For use as singleton, always returns the same instance - // static getInstance(): ClerkBackendAPI { - // if (!this._instance) { - // this._instance = new ClerkBackendAPI(); - // } - - // return this._instance; - // } - - // Setters for the embedded rest client - set apiKey(value: string) { - this._restClient.apiKey = value; - } - - set serverApiUrl(value: string) { - this._restClient.serverApiUrl = value; - } - - set apiVersion(value: string) { - this._restClient.apiVersion = value; - } - - // Lazy sub-api getters - get allowlistIdentifiers(): AllowlistIdentifierApi { - if (!this._allowlistIdentifierApi) { - this._allowlistIdentifierApi = new AllowlistIdentifierApi(this._restClient); - } - return this._allowlistIdentifierApi; - } - - get clients(): ClientApi { - if (!this._clientApi) { - this._clientApi = new ClientApi(this._restClient); - } - - return this._clientApi; - } - - get emails(): EmailApi { - if (!this._emailApi) { - this._emailApi = new EmailApi(this._restClient); - } - - return this._emailApi; - } - - get invitations(): InvitationApi { - if (!this._invitationApi) { - this._invitationApi = new InvitationApi(this._restClient); - } - return this._invitationApi; - } - - get organizations(): OrganizationApi { - if (!this._organizationApi) { - this._organizationApi = new OrganizationApi(this._restClient); - } - return this._organizationApi; - } - - get sessions(): SessionApi { - if (!this._sessionApi) { - this._sessionApi = new SessionApi(this._restClient); - } - - return this._sessionApi; - } - - get smsMessages(): SMSMessageApi { - if (!this._smsMessageApi) { - this._smsMessageApi = new SMSMessageApi(this._restClient); - } - - return this._smsMessageApi; - } - - get users(): UserApi { - if (!this._userApi) { - this._userApi = new UserApi(this._restClient); - } - - return this._userApi; - } -} diff --git a/packages/backend-core/src/api/ClerkBackendApi.ts b/packages/backend-core/src/api/ClerkBackendApi.ts new file mode 100644 index 00000000000..9e93150f668 --- /dev/null +++ b/packages/backend-core/src/api/ClerkBackendApi.ts @@ -0,0 +1,216 @@ +import snakecaseKeys from 'snakecase-keys'; + +import { joinPaths } from '../util/path'; +import { APIClient, APIRequestOptions } from './endpoints'; +import { + AllowlistIdentifierAPI, + ClientAPI, + EmailAPI, + InvitationAPI, + OrganizationAPI, + RedirectUrlAPI, + SessionAPI, + SignInTokenAPI, + SMSMessageAPI, + UserAPI, +} from './endpoints'; +import { MISSING_API_CLIENT_ERROR, MISSING_API_KEY } from './errors'; +import { deserialize } from './resources/Deserializer'; + +const DEFAULT_API_KEY = process.env.CLERK_API_KEY || ''; +const DEFAULT_API_URL = process.env.CLERK_API_URL || 'https://api.clerk.dev'; +const DEFAULT_API_VERSION = process.env.CLERK_API_VERSION || 'v1'; + +const INTERSTITIAL_URL = `${DEFAULT_API_URL}/${DEFAULT_API_VERSION}/internal/interstitial`; + +type ClerkBackendAPIOptions = { + /* Backend API Client (not specific to window.fetch API) + * The fetcher implementation should return the response body, not the whole response. + */ + apiClient: APIClient; + /* Backend API key */ + apiKey?: string; + /* Backend API URL */ + apiUrl?: string; + /* Backend API version */ + apiVersion?: string; + /* Library/SDK name */ + libName: string; + /* Library/SDK semver version string */ + libVersion: string; +}; + +export class ClerkBackendAPI { + private _allowlistIdentifierAPI?: AllowlistIdentifierAPI; + private _clientAPI?: ClientAPI; + private _emailAPI?: EmailAPI; + private _invitationAPI?: InvitationAPI; + private _organizationAPI?: OrganizationAPI; + private _redirectUrlAPI?: RedirectUrlAPI; + private _sessionAPI?: SessionAPI; + private _signInTokenAPI?: SignInTokenAPI; + private _smsMessageAPI?: SMSMessageAPI; + private _userAPI?: UserAPI; + + apiClient: APIClient; + apiKey: string; + apiUrl: string; + apiVersion: string; + userAgent: string; + + constructor(public options: ClerkBackendAPIOptions) { + const { + apiClient, + apiKey = DEFAULT_API_KEY, + apiUrl = DEFAULT_API_URL, + apiVersion = DEFAULT_API_VERSION, + libName, + libVersion, + } = options; + + if (!apiClient) { + throw Error(MISSING_API_CLIENT_ERROR); + } + + this.apiClient = apiClient; + this.apiKey = apiKey; + this.apiUrl = apiUrl; + this.apiVersion = apiVersion; + + this.userAgent = `${libName}@${libVersion}`; + } + + async request(requestOptions: APIRequestOptions) { + if (!this.apiKey) { + throw Error(MISSING_API_KEY); + } + + const { path, method } = requestOptions; + const url = joinPaths(this.apiUrl, this.apiVersion, path); + + const newRequestOptions = { method, url } as APIRequestOptions; + + newRequestOptions.queryParams = snakecaseKeys({ + ...this.getDefaultQueryParams(), + ...requestOptions.queryParams, + }); + + newRequestOptions.headerParams = { + ...this.getDefaultHeadersParams(), + ...requestOptions.headerParams, + }; + + newRequestOptions.bodyParams = snakecaseKeys({ + ...this.getDefaultBodyParams(), + ...requestOptions.bodyParams, + }); + + const data = await this.apiClient.request(newRequestOptions); + + return deserialize(data) as T; + } + + fetchInterstitial() { + return this.apiClient.request({ + url: INTERSTITIAL_URL, + method: 'GET', + headerParams: { + ...this.getDefaultHeadersParams(), + 'Content-Type': 'text/html', + }, + }); + } + + getDefaultQueryParams() { + return {}; + } + + getDefaultHeadersParams() { + return { + Authorization: `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json', + 'User-Agent': this.userAgent, + 'X-Clerk-SDK': this.userAgent, + }; + } + + getDefaultBodyParams() { + return {}; + } + + // Lazy sub-API getters + get allowlistIdentifiers(): AllowlistIdentifierAPI { + if (!this._allowlistIdentifierAPI) { + this._allowlistIdentifierAPI = new AllowlistIdentifierAPI(this); + } + return this._allowlistIdentifierAPI; + } + + get clients(): ClientAPI { + if (!this._clientAPI) { + this._clientAPI = new ClientAPI(this); + } + + return this._clientAPI; + } + + get emails(): EmailAPI { + if (!this._emailAPI) { + this._emailAPI = new EmailAPI(this); + } + + return this._emailAPI; + } + + get invitations(): InvitationAPI { + if (!this._invitationAPI) { + this._invitationAPI = new InvitationAPI(this); + } + return this._invitationAPI; + } + + get organizations(): OrganizationAPI { + if (!this._organizationAPI) { + this._organizationAPI = new OrganizationAPI(this); + } + return this._organizationAPI; + } + + get redirectUrls(): RedirectUrlAPI { + if (!this._redirectUrlAPI) { + this._redirectUrlAPI = new RedirectUrlAPI(this); + } + return this._redirectUrlAPI; + } + + get sessions(): SessionAPI { + if (!this._sessionAPI) { + this._sessionAPI = new SessionAPI(this); + } + + return this._sessionAPI; + } + + get signInTokens(): SignInTokenAPI { + if (!this._signInTokenAPI) { + this._signInTokenAPI = new SignInTokenAPI(this); + } + return this._signInTokenAPI; + } + + get smsMessages(): SMSMessageAPI { + if (!this._smsMessageAPI) { + this._smsMessageAPI = new SMSMessageAPI(this); + } + + return this._smsMessageAPI; + } + + get users(): UserAPI { + if (!this._userAPI) { + this._userAPI = new UserAPI(this); + } + + return this._userAPI; + } +} diff --git a/packages/backend-core/src/api/collection/AbstractApi.ts b/packages/backend-core/src/api/collection/AbstractApi.ts deleted file mode 100644 index d04dbc96264..00000000000 --- a/packages/backend-core/src/api/collection/AbstractApi.ts +++ /dev/null @@ -1,15 +0,0 @@ -import RestClient from '../utils/RestClient'; - -export abstract class AbstractApi { - protected _restClient: RestClient; - - constructor(restClient: RestClient) { - this._restClient = restClient; - } - - protected requireId(id: string) { - if (!id) { - throw new Error('A valid ID is required.'); - } - } -} diff --git a/packages/backend-core/src/api/collection/ClientApi.ts b/packages/backend-core/src/api/collection/ClientApi.ts deleted file mode 100644 index 1045b57b485..00000000000 --- a/packages/backend-core/src/api/collection/ClientApi.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Client } from '../resources/Client'; -import { AbstractApi } from './AbstractApi'; - -export class ClientApi extends AbstractApi { - public async getClientList() { - return this._restClient.makeRequest>({ - method: 'GET', - path: '/clients', - }); - } - - public async getClient(clientId: string) { - this.requireId(clientId); - return this._restClient.makeRequest({ - method: 'GET', - path: `/clients/${clientId}`, - }); - } - - public verifyClient(token: string) { - return this._restClient.makeRequest({ - method: 'POST', - path: '/clients/verify', - bodyParams: { token }, - }); - } -} diff --git a/packages/backend-core/src/api/collection/UserApi.ts b/packages/backend-core/src/api/collection/UserApi.ts deleted file mode 100644 index f9a0b38495c..00000000000 --- a/packages/backend-core/src/api/collection/UserApi.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { User } from '../resources/User'; -import { AbstractApi } from './AbstractApi'; - -interface UserParams { - firstName?: string; - lastName?: string; - username?: string; - password?: string; - primaryEmailAddressID?: string; - primaryPhoneNumberID?: string; - publicMetadata?: Record | string; - privateMetadata?: Record | string; - unsafeMetadata?: Record | string; -} - -type UserCountParams = { - emailAddress?: string[]; - phoneNumber?: string[]; - username?: string[]; - web3Wallet?: string[]; - query?: string; - userId?: string[]; -}; - -type UserListParams = UserCountParams & { - limit?: number; - offset?: number; - orderBy?: 'created_at' | 'updated_at' | '+created_at' | '+updated_at' | '-created_at' | '-updated_at'; -}; - -const userMetadataKeys = ['publicMetadata', 'privateMetadata', 'unsafeMetadata']; - -type UserMetadataParams = { - publicMetadata?: Record; - privateMetadata?: Record; - unsafeMetadata?: Record; -}; - -type CreateUserParams = { - externalId?: string; - emailAddress?: string[]; - phoneNumber?: string[]; - username?: string; - password?: string; - firstName?: string; - lastName?: string; - skipPasswordChecks?: boolean; - skipPasswordRequirement?: boolean; -} & UserMetadataParams; - -type UserMetadataRequestBody = { - publicMetadata?: string; - privateMetadata?: string; - unsafeMetadata?: string; -}; - -export class UserApi extends AbstractApi { - public async getUserList(params: UserListParams = {}) { - return this._restClient.makeRequest>({ - method: 'GET', - path: '/users', - queryParams: params, - }); - } - - public async getUser(userId: string) { - this.requireId(userId); - return this._restClient.makeRequest({ - method: 'GET', - path: `/users/${userId}`, - }); - } - - public async createUser(params: CreateUserParams): Promise { - const { publicMetadata, privateMetadata, unsafeMetadata } = params; - return this._restClient.makeRequest({ - method: 'POST', - path: '/users', - bodyParams: { - ...params, - ...stringifyMetadataParams({ - publicMetadata, - privateMetadata, - unsafeMetadata, - }), - }, - }); - } - - public async updateUser(userId: string, params: UserParams = {}) { - this.requireId(userId); - - // The Clerk server API requires metadata fields to be stringified - if (params.publicMetadata && !(typeof params.publicMetadata == 'string')) { - params.publicMetadata = JSON.stringify(params.publicMetadata); - } - - if (params.privateMetadata && !(typeof params.privateMetadata == 'string')) { - params.privateMetadata = JSON.stringify(params.privateMetadata); - } - - if (params.unsafeMetadata && !(typeof params.unsafeMetadata == 'string')) { - params.unsafeMetadata = JSON.stringify(params.unsafeMetadata); - } - - return this._restClient.makeRequest({ - method: 'PATCH', - path: `/users/${userId}`, - bodyParams: params, - }); - } - - public async deleteUser(userId: string) { - this.requireId(userId); - return this._restClient.makeRequest({ - method: 'DELETE', - path: `/users/${userId}`, - }); - } - - public async getCount(params: UserListParams = {}) { - return this._restClient.makeRequest({ - method: 'GET', - path: '/users/count', - queryParams: params, - }); - } - - public async getUserOauthAccessToken(userId: string, provider: `oauth_${string}`) { - this.requireId(userId); - return this._restClient.makeRequest({ - method: 'GET', - path: `/users/${userId}/oauth_access_tokens/${provider}`, - }); - } -} - -function stringifyMetadataParams( - params: UserMetadataParams & { - [key: string]: Record | undefined; - }, -): UserMetadataRequestBody { - return userMetadataKeys.reduce((res: Record, key: string): Record => { - if (params[key]) { - res[key] = JSON.stringify(params[key]); - } - return res; - }, {}); -} diff --git a/packages/backend-core/src/api/endpoints/AbstractApi.ts b/packages/backend-core/src/api/endpoints/AbstractApi.ts new file mode 100644 index 00000000000..3c5dcb1c5a8 --- /dev/null +++ b/packages/backend-core/src/api/endpoints/AbstractApi.ts @@ -0,0 +1,29 @@ +export type APIRequestOptions = { + method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT'; + queryParams?: Record; + headerParams?: Record; + bodyParams?: object; +} & ( + | { + url: string; + path?: string; + } + | { + url?: string; + path: string; + } +); + +export interface APIClient { + request: (options: APIRequestOptions) => Promise; +} + +export abstract class AbstractAPI { + constructor(protected APIClient: APIClient) {} + + protected requireId(id: string) { + if (!id) { + throw new Error('A valid resource ID is required.'); + } + } +} diff --git a/packages/backend-core/src/api/collection/AllowlistIdentifierApi.ts b/packages/backend-core/src/api/endpoints/AllowlistIdentifierApi.ts similarity index 62% rename from packages/backend-core/src/api/collection/AllowlistIdentifierApi.ts rename to packages/backend-core/src/api/endpoints/AllowlistIdentifierApi.ts index 1b93056849c..9ce53c40638 100644 --- a/packages/backend-core/src/api/collection/AllowlistIdentifierApi.ts +++ b/packages/backend-core/src/api/endpoints/AllowlistIdentifierApi.ts @@ -1,5 +1,6 @@ +import { joinPaths } from '../../util/path'; import { AllowlistIdentifier } from '../resources/AllowlistIdentifier'; -import { AbstractApi } from './AbstractApi'; +import { AbstractAPI } from './AbstractApi'; const basePath = '/allowlist_identifiers'; @@ -8,16 +9,16 @@ type AllowlistIdentifierCreateParams = { notify: boolean; }; -export class AllowlistIdentifierApi extends AbstractApi { +export class AllowlistIdentifierAPI extends AbstractAPI { public async getAllowlistIdentifierList() { - return this._restClient.makeRequest>({ + return this.APIClient.request>({ method: 'GET', path: basePath, }); } public async createAllowlistIdentifier(params: AllowlistIdentifierCreateParams) { - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', path: basePath, bodyParams: params, @@ -26,9 +27,9 @@ export class AllowlistIdentifierApi extends AbstractApi { public async deleteAllowlistIdentifier(allowlistIdentifierId: string) { this.requireId(allowlistIdentifierId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'DELETE', - path: `${basePath}/${allowlistIdentifierId}`, + path: joinPaths(basePath, allowlistIdentifierId), }); } } diff --git a/packages/backend-core/src/api/endpoints/ClientApi.ts b/packages/backend-core/src/api/endpoints/ClientApi.ts new file mode 100644 index 00000000000..47801dced8d --- /dev/null +++ b/packages/backend-core/src/api/endpoints/ClientApi.ts @@ -0,0 +1,30 @@ +import { joinPaths } from '../../util/path'; +import { Client } from '../resources/Client'; +import { AbstractAPI } from './AbstractApi'; + +const basePath = '/clients'; + +export class ClientAPI extends AbstractAPI { + public async getClientList() { + return this.APIClient.request>({ + method: 'GET', + path: basePath, + }); + } + + public async getClient(clientId: string) { + this.requireId(clientId); + return this.APIClient.request({ + method: 'GET', + path: joinPaths(basePath, clientId), + }); + } + + public verifyClient(token: string) { + return this.APIClient.request({ + method: 'POST', + path: joinPaths(basePath, 'verify'), + bodyParams: { token }, + }); + } +} diff --git a/packages/backend-core/src/api/collection/EmailApi.ts b/packages/backend-core/src/api/endpoints/EmailApi.ts similarity index 59% rename from packages/backend-core/src/api/collection/EmailApi.ts rename to packages/backend-core/src/api/endpoints/EmailApi.ts index f53e156e70a..252cf047a97 100644 --- a/packages/backend-core/src/api/collection/EmailApi.ts +++ b/packages/backend-core/src/api/endpoints/EmailApi.ts @@ -1,5 +1,5 @@ import { Email } from '../resources/Email'; -import { AbstractApi } from './AbstractApi'; +import { AbstractAPI } from './AbstractApi'; type EmailParams = { fromEmailName: string; @@ -7,11 +7,14 @@ type EmailParams = { subject: string; body: string; }; -export class EmailApi extends AbstractApi { + +const basePath = '/emails'; + +export class EmailAPI extends AbstractAPI { public async createEmail(params: EmailParams) { - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', - path: '/emails', + path: basePath, bodyParams: params, }); } diff --git a/packages/backend-core/src/api/collection/InvitationApi.ts b/packages/backend-core/src/api/endpoints/InvitationApi.ts similarity index 65% rename from packages/backend-core/src/api/collection/InvitationApi.ts rename to packages/backend-core/src/api/endpoints/InvitationApi.ts index a2971164db0..bb1153c4ab4 100644 --- a/packages/backend-core/src/api/collection/InvitationApi.ts +++ b/packages/backend-core/src/api/endpoints/InvitationApi.ts @@ -1,5 +1,6 @@ +import { joinPaths } from '../../util/path'; import { Invitation } from '../resources/Invitation'; -import { AbstractApi } from './AbstractApi'; +import { AbstractAPI } from './AbstractApi'; const basePath = '/invitations'; @@ -9,16 +10,16 @@ type CreateParams = { publicMetadata?: Record; }; -export class InvitationApi extends AbstractApi { +export class InvitationAPI extends AbstractAPI { public async getInvitationList() { - return this._restClient.makeRequest>({ + return this.APIClient.request>({ method: 'GET', path: basePath, }); } public async createInvitation(params: CreateParams) { - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', path: basePath, bodyParams: { @@ -30,9 +31,9 @@ export class InvitationApi extends AbstractApi { public async revokeInvitation(invitationId: string) { this.requireId(invitationId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', - path: `${basePath}/${invitationId}/revoke`, + path: joinPaths(basePath, invitationId, 'revoke'), }); } } diff --git a/packages/backend-core/src/api/collection/OrganizationApi.ts b/packages/backend-core/src/api/endpoints/OrganizationApi.ts similarity index 64% rename from packages/backend-core/src/api/collection/OrganizationApi.ts rename to packages/backend-core/src/api/endpoints/OrganizationApi.ts index 3c44c76c767..3d6fcef1d65 100644 --- a/packages/backend-core/src/api/collection/OrganizationApi.ts +++ b/packages/backend-core/src/api/endpoints/OrganizationApi.ts @@ -1,6 +1,7 @@ +import { joinPaths } from '../../util/path'; import { Organization, OrganizationInvitation, OrganizationMembership } from '../resources'; import { OrganizationMembershipRole } from '../resources/Enums'; -import { AbstractApi } from './AbstractApi'; +import { AbstractAPI } from './AbstractApi'; const basePath = '/organizations'; @@ -17,14 +18,10 @@ type GetOrganizationListParams = { type CreateParams = { name: string; slug?: string; + /* The User id for the user creating the organization. The user will become an administrator for the organization. */ createdBy: string; } & OrganizationMetadataParams; -type OrganizationMetadataRequestBody = { - publicMetadata?: string; - privateMetadata?: string; -}; - type GetOrganizationParams = { organizationId: string } | { slug: string }; type UpdateParams = { @@ -72,9 +69,9 @@ type RevokeOrganizationInvitationParams = { requestingUserId: string; }; -export class OrganizationApi extends AbstractApi { +export class OrganizationAPI extends AbstractAPI { public async getOrganizationList(params?: GetOrganizationListParams) { - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'GET', path: basePath, queryParams: params, @@ -82,17 +79,10 @@ export class OrganizationApi extends AbstractApi { } public async createOrganization(params: CreateParams) { - const { publicMetadata, privateMetadata } = params; - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', path: basePath, - bodyParams: { - ...params, - ...stringifyMetadataParams({ - publicMetadata, - privateMetadata, - }), - }, + bodyParams: params, }); } @@ -100,17 +90,17 @@ export class OrganizationApi extends AbstractApi { const organizationIdOrSlug = 'organizationId' in params ? params.organizationId : params.slug; this.requireId(organizationIdOrSlug); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'GET', - path: `${basePath}/${organizationIdOrSlug}`, + path: joinPaths(basePath, organizationIdOrSlug), }); } public async updateOrganization(organizationId: string, params: UpdateParams) { this.requireId(organizationId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'PATCH', - path: `${basePath}/${organizationId}`, + path: joinPaths(basePath, organizationId), bodyParams: params, }); } @@ -118,17 +108,17 @@ export class OrganizationApi extends AbstractApi { public async updateOrganizationMetadata(organizationId: string, params: UpdateMetadataParams) { this.requireId(organizationId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'PATCH', - path: `${basePath}/${organizationId}/metadata`, - bodyParams: stringifyMetadataParams(params), + path: joinPaths(basePath, organizationId, 'metadata'), + bodyParams: params, }); } public async deleteOrganization(organizationId: string) { - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'DELETE', - path: `${basePath}/${organizationId}`, + path: joinPaths(basePath, organizationId), }); } @@ -136,9 +126,9 @@ export class OrganizationApi extends AbstractApi { const { organizationId, limit, offset } = params; this.requireId(organizationId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'GET', - path: `${basePath}/${organizationId}/memberships`, + path: joinPaths(basePath, organizationId, 'memberships'), queryParams: { limit, offset }, }); } @@ -147,9 +137,9 @@ export class OrganizationApi extends AbstractApi { const { organizationId, userId, role } = params; this.requireId(organizationId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', - path: `${basePath}/${organizationId}/memberships`, + path: joinPaths(basePath, organizationId, 'memberships'), bodyParams: { userId, role, @@ -161,9 +151,9 @@ export class OrganizationApi extends AbstractApi { const { organizationId, userId, role } = params; this.requireId(organizationId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'PATCH', - path: `${basePath}/${organizationId}/memberships/${userId}`, + path: joinPaths(basePath, organizationId, 'memberships', userId), bodyParams: { role, }, @@ -174,9 +164,9 @@ export class OrganizationApi extends AbstractApi { const { organizationId, userId } = params; this.requireId(organizationId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'DELETE', - path: `${basePath}/${organizationId}/memberships/${userId}`, + path: joinPaths(basePath, organizationId, 'memberships', userId), }); } @@ -184,9 +174,9 @@ export class OrganizationApi extends AbstractApi { const { organizationId, limit, offset } = params; this.requireId(organizationId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'GET', - path: `${basePath}/${organizationId}/invitations/pending`, + path: joinPaths(basePath, organizationId, 'invitations', 'pending'), queryParams: { limit, offset }, }); } @@ -195,10 +185,10 @@ export class OrganizationApi extends AbstractApi { const { organizationId, ...bodyParams } = params; this.requireId(organizationId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', - path: `${basePath}/${organizationId}/invitations`, - bodyParams, + path: joinPaths(basePath, organizationId, 'invitations'), + bodyParams: { ...bodyParams }, }); } @@ -206,28 +196,12 @@ export class OrganizationApi extends AbstractApi { const { organizationId, invitationId, requestingUserId } = params; this.requireId(organizationId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', - path: `${basePath}/${organizationId}/invitations/${invitationId}/revoke`, + path: joinPaths(basePath, organizationId, 'invitations', invitationId, 'revoke'), bodyParams: { requestingUserId, }, }); } } - -function stringifyMetadataParams( - params: OrganizationMetadataParams & { - [key: string]: Record | undefined; - }, -): OrganizationMetadataRequestBody { - return ['publicMetadata', 'privateMetadata'].reduce( - (res: Record, key: string): Record => { - if (params[key]) { - res[key] = JSON.stringify(params[key]); - } - return res; - }, - {}, - ); -} diff --git a/packages/backend-core/src/api/endpoints/RedirectUrlApi.ts b/packages/backend-core/src/api/endpoints/RedirectUrlApi.ts new file mode 100644 index 00000000000..58472116701 --- /dev/null +++ b/packages/backend-core/src/api/endpoints/RedirectUrlApi.ts @@ -0,0 +1,42 @@ +import { joinPaths } from '../../util/path'; +import { RedirectUrl } from '../resources/RedirectUrl'; +import { AbstractAPI } from './AbstractApi'; + +const basePath = '/redirect_urls'; + +type CreateRedirectUrlParams = { + url: string; +}; + +export class RedirectUrlAPI extends AbstractAPI { + public async getRedirectUrlList() { + return this.APIClient.request>({ + method: 'GET', + path: basePath, + }); + } + + public async getRedirectUrl(redirectUrlId: string) { + this.requireId(redirectUrlId); + return this.APIClient.request({ + method: 'GET', + path: joinPaths(basePath, redirectUrlId), + }); + } + + public async createRedirectUrl(params: CreateRedirectUrlParams): Promise { + return this.APIClient.request({ + method: 'POST', + path: basePath, + bodyParams: params, + }); + } + + public async deleteRedirectUrl(redirectUrlId: string) { + this.requireId(redirectUrlId); + return this.APIClient.request({ + method: 'DELETE', + path: joinPaths(basePath, redirectUrlId), + }); + } +} diff --git a/packages/backend-core/src/api/collection/SMSMessageApi.ts b/packages/backend-core/src/api/endpoints/SMSMessageApi.ts similarity index 54% rename from packages/backend-core/src/api/collection/SMSMessageApi.ts rename to packages/backend-core/src/api/endpoints/SMSMessageApi.ts index 71da962eeaa..f2742100347 100644 --- a/packages/backend-core/src/api/collection/SMSMessageApi.ts +++ b/packages/backend-core/src/api/endpoints/SMSMessageApi.ts @@ -1,16 +1,18 @@ import { SMSMessage } from '../resources/SMSMessage'; -import { AbstractApi } from './AbstractApi'; +import { AbstractAPI } from './AbstractApi'; + +const basePath = '/sms_messages'; type SMSParams = { phoneNumberId: string; message: string; }; -export class SMSMessageApi extends AbstractApi { +export class SMSMessageAPI extends AbstractAPI { public async createSMSMessage(params: SMSParams) { - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', - path: '/sms_messages', + path: basePath, bodyParams: params, }); } diff --git a/packages/backend-core/src/api/collection/SessionApi.ts b/packages/backend-core/src/api/endpoints/SessionApi.ts similarity index 57% rename from packages/backend-core/src/api/collection/SessionApi.ts rename to packages/backend-core/src/api/endpoints/SessionApi.ts index 6f1a6567b11..3d14e6245fc 100644 --- a/packages/backend-core/src/api/collection/SessionApi.ts +++ b/packages/backend-core/src/api/endpoints/SessionApi.ts @@ -1,41 +1,44 @@ +import { joinPaths } from '../../util/path'; import { Session } from '../resources/Session'; import { Token } from '../resources/Token'; -import { AbstractApi } from './AbstractApi'; +import { AbstractAPI } from './AbstractApi'; + +const basePath = '/sessions'; type QueryParams = { clientId?: string; userId?: string; }; -export class SessionApi extends AbstractApi { +export class SessionAPI extends AbstractAPI { public getSessionList = async (queryParams?: QueryParams) => - this._restClient.makeRequest>({ + this.APIClient.request>({ method: 'GET', - path: '/sessions', + path: basePath, queryParams: queryParams, }); public getSession = async (sessionId: string) => { this.requireId(sessionId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'GET', - path: `/sessions/${sessionId}`, + path: joinPaths(basePath, sessionId), }); }; public revokeSession = async (sessionId: string) => { this.requireId(sessionId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', - path: `/sessions/${sessionId}/revoke`, + path: joinPaths(basePath, sessionId, 'revoke'), }); }; public verifySession = async (sessionId: string, token: string) => { this.requireId(sessionId); - return this._restClient.makeRequest({ + return this.APIClient.request({ method: 'POST', - path: `/sessions/${sessionId}/verify`, + path: joinPaths(basePath, sessionId, 'verify'), bodyParams: { token }, }); }; @@ -43,9 +46,9 @@ export class SessionApi extends AbstractApi { public getToken = async (sessionId: string, template: string) => { this.requireId(sessionId); return ( - (await this._restClient.makeRequest({ + (await this.APIClient.request({ method: 'POST', - path: `/sessions/${sessionId}/tokens/${template || ''}`, + path: joinPaths(basePath, sessionId, 'tokens', template || ''), })) as any ).jwt; }; diff --git a/packages/backend-core/src/api/endpoints/SignInTokenApi.ts b/packages/backend-core/src/api/endpoints/SignInTokenApi.ts new file mode 100644 index 00000000000..69ef44f096d --- /dev/null +++ b/packages/backend-core/src/api/endpoints/SignInTokenApi.ts @@ -0,0 +1,28 @@ +import { joinPaths } from '../../util/path'; +import { SignInToken } from '../resources/SignInTokens'; +import { AbstractAPI } from './AbstractApi'; + +type CreateSignInTokensParams = { + userId: string; + expiresInSeconds: number; +}; + +const basePath = '/sign_in_tokens'; + +export class SignInTokenAPI extends AbstractAPI { + public async createSignInToken(params: CreateSignInTokensParams) { + return this.APIClient.request({ + method: 'POST', + path: basePath, + bodyParams: params, + }); + } + + public async revokeSignInToken(signInTokenId: string) { + this.requireId(signInTokenId); + return this.APIClient.request({ + method: 'POST', + path: joinPaths(basePath, signInTokenId, 'revoke'), + }); + } +} diff --git a/packages/backend-core/src/api/endpoints/UserApi.ts b/packages/backend-core/src/api/endpoints/UserApi.ts new file mode 100644 index 00000000000..c2bed75afc7 --- /dev/null +++ b/packages/backend-core/src/api/endpoints/UserApi.ts @@ -0,0 +1,117 @@ +import { joinPaths } from '../../util/path'; +import { User } from '../resources/User'; +import { AbstractAPI } from './AbstractApi'; + +const basePath = '/users'; + +type UserCountParams = { + emailAddress?: string[]; + phoneNumber?: string[]; + username?: string[]; + web3Wallet?: string[]; + query?: string; + userId?: string[]; +}; + +type UserListParams = UserCountParams & { + limit?: number; + offset?: number; + orderBy?: 'created_at' | 'updated_at' | '+created_at' | '+updated_at' | '-created_at' | '-updated_at'; +}; + +type UserMetadataParams = { + publicMetadata?: Record; + privateMetadata?: Record; + unsafeMetadata?: Record; +}; + +type CreateUserParams = { + externalId?: string; + emailAddress?: string[]; + phoneNumber?: string[]; + username?: string; + password?: string; + firstName?: string; + lastName?: string; + skipPasswordChecks?: boolean; + skipPasswordRequirement?: boolean; +} & UserMetadataParams; + +interface UpdateUserParams extends UserMetadataParams { + firstName?: string; + lastName?: string; + username?: string; + password?: string; + primaryEmailAddressID?: string; + primaryPhoneNumberID?: string; +} + +export class UserAPI extends AbstractAPI { + public async getUserList(params: UserListParams = {}) { + return this.APIClient.request>({ + method: 'GET', + path: basePath, + queryParams: params, + }); + } + + public async getUser(userId: string) { + this.requireId(userId); + return this.APIClient.request({ + method: 'GET', + path: joinPaths(basePath, userId), + }); + } + + public async createUser(params: CreateUserParams): Promise { + return this.APIClient.request({ + method: 'POST', + path: basePath, + bodyParams: params, + }); + } + + public async updateUser(userId: string, params: UpdateUserParams = {}) { + this.requireId(userId); + + return this.APIClient.request({ + method: 'PATCH', + path: joinPaths(basePath, userId), + bodyParams: params, + }); + } + + public async updateUserMetadata(userId: string, params: UserMetadataParams) { + this.requireId(userId); + + return this.APIClient.request({ + method: 'PATCH', + path: joinPaths(basePath, userId, 'metadata'), + bodyParams: params, + }); + } + + public async deleteUser(userId: string) { + this.requireId(userId); + return this.APIClient.request({ + method: 'DELETE', + path: joinPaths(basePath, userId), + }); + } + + public async getCount(params: UserListParams = {}) { + return this.APIClient.request({ + method: 'GET', + path: joinPaths(basePath, 'count'), + queryParams: params, + }); + } + + public async getUserOauthAccessToken(userId: string, provider: `oauth_${string}`) { + this.requireId(userId); + return this.APIClient.request({ + method: 'GET', + path: joinPaths(basePath, userId, 'oauth_access_tokens', provider), + }); + } +} diff --git a/packages/backend-core/src/api/collection/index.ts b/packages/backend-core/src/api/endpoints/index.ts similarity index 80% rename from packages/backend-core/src/api/collection/index.ts rename to packages/backend-core/src/api/endpoints/index.ts index 67683873002..589abc9e3f8 100644 --- a/packages/backend-core/src/api/collection/index.ts +++ b/packages/backend-core/src/api/endpoints/index.ts @@ -4,6 +4,8 @@ export * from './ClientApi'; export * from './EmailApi'; export * from './InvitationApi'; export * from './OrganizationApi'; -export * from './SMSMessageApi'; +export * from './RedirectUrlApi'; export * from './SessionApi'; +export * from './SignInTokenApi'; +export * from './SMSMessageApi'; export * from './UserApi'; diff --git a/packages/backend-core/src/api/errors/ErrorMessages.ts b/packages/backend-core/src/api/errors/ErrorMessages.ts new file mode 100644 index 00000000000..76f9e589d29 --- /dev/null +++ b/packages/backend-core/src/api/errors/ErrorMessages.ts @@ -0,0 +1,3 @@ +export const MISSING_API_CLIENT_ERROR = 'Missing API client option.'; +export const MISSING_API_KEY = + 'Missing Clerk API Key. Go to https://dashboard.clerk.dev and get your Clerk API Key for your instance.'; diff --git a/packages/backend-core/src/api/errors/Errors.ts b/packages/backend-core/src/api/errors/Errors.ts new file mode 100644 index 00000000000..b1bdb7ff157 --- /dev/null +++ b/packages/backend-core/src/api/errors/Errors.ts @@ -0,0 +1,37 @@ +import type { ClerkAPIError, ClerkAPIErrorJSON } from '@clerk/types'; + +export function parseErrors(data: ClerkAPIErrorJSON[] = []): ClerkAPIError[] { + return data.length > 0 ? data.map(parseError) : []; +} + +export function parseError(error: ClerkAPIErrorJSON): ClerkAPIError { + return { + code: error.code, + message: error.message, + longMessage: error.long_message, + meta: { + paramName: error?.meta?.param_name, + sessionId: error?.meta?.session_id, + }, + }; +} + +export class ClerkAPIResponseError extends Error { + clerkError: true; + + status: number; + message: string; + + errors: ClerkAPIError[]; + + constructor(message: string, { data, status }: { data: any; status: number }) { + super(message); + + Object.setPrototypeOf(this, ClerkAPIResponseError.prototype); + + this.clerkError = true; + this.message = message; + this.status = status; + this.errors = parseErrors(data); + } +} diff --git a/packages/backend-core/src/api/errors/index.ts b/packages/backend-core/src/api/errors/index.ts new file mode 100644 index 00000000000..0027a95431a --- /dev/null +++ b/packages/backend-core/src/api/errors/index.ts @@ -0,0 +1,2 @@ +export * from './ErrorMessages'; +export * from './Errors'; diff --git a/packages/backend-core/src/api/index.ts b/packages/backend-core/src/api/index.ts new file mode 100644 index 00000000000..0bcc27a3019 --- /dev/null +++ b/packages/backend-core/src/api/index.ts @@ -0,0 +1,6 @@ +export * from './ClerkBackendApi'; +export * from './resources'; +export { ClerkAPIResponseError } from './errors'; + +export type { APIClient, APIRequestOptions } from './endpoints/AbstractApi'; +export type { Session } from './resources/Session'; diff --git a/packages/backend-core/src/api/utils/Deserializer.ts b/packages/backend-core/src/api/resources/Deserializer.ts similarity index 83% rename from packages/backend-core/src/api/utils/Deserializer.ts rename to packages/backend-core/src/api/resources/Deserializer.ts index f4904d38266..1a8dc7b4499 100644 --- a/packages/backend-core/src/api/utils/Deserializer.ts +++ b/packages/backend-core/src/api/resources/Deserializer.ts @@ -1,3 +1,4 @@ +import { Logger } from '../../util/Logger'; import { AllowlistIdentifier, Client, @@ -6,16 +7,17 @@ import { Organization, OrganizationInvitation, OrganizationMembership, + RedirectUrl, Session, + SignInToken, SMSMessage, Token, User, -} from '../resources'; -import { ObjectType } from '../resources/JSON'; -import Logger from './Logger'; +} from '.'; +import { ObjectType } from './JSON'; // FIXME don't return any -export default function deserialize(payload: any): any { +export function deserialize(payload: any): any { if (Array.isArray(payload)) { return payload.map(item => jsonToObject(item)); } else if (isPaginated(payload)) { @@ -54,8 +56,10 @@ function jsonToObject(item: any): any { return OrganizationInvitation.fromJSON(item); case ObjectType.OrganizationMembership: return OrganizationMembership.fromJSON(item); - case ObjectType.User: - return User.fromJSON(item); + case ObjectType.RedirectUrl: + return RedirectUrl.fromJSON(item); + case ObjectType.SignInToken: + return SignInToken.fromJSON(item); case ObjectType.Session: return Session.fromJSON(item); case ObjectType.SmsMessage: @@ -64,7 +68,10 @@ function jsonToObject(item: any): any { return Token.fromJSON(item); case ObjectType.TotalCount: return getCount(item); + case ObjectType.User: + return User.fromJSON(item); default: Logger.error(`Unexpected object type: ${item.object}`); + return item; } } diff --git a/packages/backend-core/src/api/resources/JSON.ts b/packages/backend-core/src/api/resources/JSON.ts index 7b758e61327..66583a807d6 100644 --- a/packages/backend-core/src/api/resources/JSON.ts +++ b/packages/backend-core/src/api/resources/JSON.ts @@ -22,8 +22,10 @@ export enum ObjectType { OrganizationInvitation = 'organization_invitation', OrganizationMembership = 'organization_membership', PhoneNumber = 'phone_number', + RedirectUrl = 'redirect_url', Session = 'session', SignInAttempt = 'sign_in_attempt', + SignInToken = 'sign_in_token', SignUpAttempt = 'sign_up_attempt', SmsMessage = 'sms_message', User = 'user', @@ -155,10 +157,11 @@ export interface PhoneNumberJSON extends ClerkResourceJSON { verification: VerificationJSON | null; } -export interface Web3WalletJSON extends ClerkResourceJSON { - object: ObjectType.Web3Wallet; - web3_wallet: string; - verification: VerificationJSON | null; +export interface RedirectUrlJSON extends ClerkResourceJSON { + object: ObjectType.RedirectUrl; + url: string; + created_at: number; + updated_at: number; } export interface SessionJSON extends ClerkResourceJSON { @@ -174,7 +177,7 @@ export interface SessionJSON extends ClerkResourceJSON { } export interface SignInJSON extends ClerkResourceJSON { - object: ObjectType.SignInAttempt; + object: ObjectType.SignInToken; status: SignInStatus; allowed_identifier_types: SignInIdentifier[]; identifier: string; @@ -184,6 +187,14 @@ export interface SignInJSON extends ClerkResourceJSON { created_session_id: string | null; } +export interface SignInTokenJSON extends ClerkResourceJSON { + user_id: string; + token: string; + status: 'pending' | 'accepted' | 'revoked'; + created_at: number; + updated_at: number; +} + export interface SignUpJSON extends ClerkResourceJSON { object: ObjectType.SignUpAttempt; status: SignUpStatus; @@ -251,3 +262,9 @@ export interface VerificationJSON extends ClerkResourceJSON { verified_at_client?: string; // error? } + +export interface Web3WalletJSON extends ClerkResourceJSON { + object: ObjectType.Web3Wallet; + web3_wallet: string; + verification: VerificationJSON | null; +} diff --git a/packages/backend-core/src/api/resources/RedirectUrl.ts b/packages/backend-core/src/api/resources/RedirectUrl.ts new file mode 100644 index 00000000000..4a3b6aef51f --- /dev/null +++ b/packages/backend-core/src/api/resources/RedirectUrl.ts @@ -0,0 +1,9 @@ +import type { RedirectUrlJSON } from './JSON'; + +export class RedirectUrl { + constructor(readonly id: string, readonly url: string, readonly createdAt: number, readonly updatedAt: number) {} + + static fromJSON(data: RedirectUrlJSON): RedirectUrl { + return new RedirectUrl(data.id, data.url, data.created_at, data.updated_at); + } +} diff --git a/packages/backend-core/src/api/resources/SignInTokens.ts b/packages/backend-core/src/api/resources/SignInTokens.ts new file mode 100644 index 00000000000..388469f5a31 --- /dev/null +++ b/packages/backend-core/src/api/resources/SignInTokens.ts @@ -0,0 +1,16 @@ +import type { SignInTokenJSON } from './JSON'; + +export class SignInToken { + constructor( + readonly id: string, + readonly userId: string, + readonly token: string, + readonly status: string, + readonly createdAt: number, + readonly updatedAt: number, + ) {} + + static fromJSON(data: SignInTokenJSON): SignInToken { + return new SignInToken(data.id, data.user_id, data.token, data.status, data.created_at, data.updated_at); + } +} diff --git a/packages/backend-core/src/api/resources/index.ts b/packages/backend-core/src/api/resources/index.ts index 6e5f4c3bcf5..644daf12451 100644 --- a/packages/backend-core/src/api/resources/index.ts +++ b/packages/backend-core/src/api/resources/index.ts @@ -1,18 +1,21 @@ export * from './AllowlistIdentifier'; export * from './Client'; +export * from './Deserializer'; export * from './Email'; export * from './EmailAddress'; export * from './Enums'; export * from './ExternalAccount'; export * from './IdentificationLink'; export * from './Invitation'; +export * from './JSON'; export * from './Organization'; export * from './OrganizationInvitation'; export * from './OrganizationMembership'; -export * from './JSON'; export * from './PhoneNumber'; +export * from './RedirectUrl'; export * from './Session'; +export * from './SignInTokens'; export * from './SMSMessage'; +export * from './Token'; export * from './User'; export * from './Verification'; -export * from './Token'; diff --git a/packages/backend-core/src/api/utils/ErrorHandler.ts b/packages/backend-core/src/api/utils/ErrorHandler.ts deleted file mode 100644 index 613f6fa6473..00000000000 --- a/packages/backend-core/src/api/utils/ErrorHandler.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ClerkServerError, ClerkServerErrorJSON, HttpError } from './Errors'; - -export default function handleError(error: any): never { - const statusCode = error?.response?.statusCode || 500; - const message = error.message || ''; - const body = error?.response?.body; - let data; - - if (body && Array.isArray(body.errors)) { - data = body.errors.map((errorJSON: ClerkServerErrorJSON) => { - return ClerkServerError.fromJSON(errorJSON); - }); - } else { - data = body; - } - - throw new HttpError(statusCode, message, data); -} diff --git a/packages/backend-core/src/api/utils/Errors.ts b/packages/backend-core/src/api/utils/Errors.ts deleted file mode 100644 index 21afe6d296d..00000000000 --- a/packages/backend-core/src/api/utils/Errors.ts +++ /dev/null @@ -1,42 +0,0 @@ -export class HttpError extends Error { - public statusCode: number; - public data: unknown; - - constructor(statusCode: number, message: string, data: unknown) { - super(message); - this.statusCode = statusCode; - this.data = data; - } -} - -export interface ClerkServerErrorProps { - message: string; - longMessage: string; - code: string; -} - -export interface ClerkServerErrorJSON { - message: string; - long_message: string; - code: string; -} - -export class ClerkServerError { - public message: string; - public longMessage: string; - public code: string; - - constructor(data: ClerkServerErrorProps) { - this.message = data.message; - this.longMessage = data.longMessage; - this.code = data.code; - } - - static fromJSON(data: ClerkServerErrorJSON) { - return new ClerkServerError({ - message: data.message, - longMessage: data.long_message, - code: data.code, - }); - } -} diff --git a/packages/backend-core/src/api/utils/Filter.ts b/packages/backend-core/src/api/utils/Filter.ts deleted file mode 100644 index 17f23b0beb5..00000000000 --- a/packages/backend-core/src/api/utils/Filter.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default function filterKeys(data: Record, allowedKeys: Array) { - return Object.keys(data) - .filter(key => allowedKeys.includes(key)) - .reduce((obj, key) => { - obj[key] = data[key]; - return obj; - }, {} as Record); -} diff --git a/packages/backend-core/src/api/utils/RestClient.ts b/packages/backend-core/src/api/utils/RestClient.ts deleted file mode 100644 index 2c986cb04c5..00000000000 --- a/packages/backend-core/src/api/utils/RestClient.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as querystring from 'query-string'; -import snakecaseKeys from 'snakecase-keys'; - -import deserialize from './Deserializer'; -import handleError from './ErrorHandler'; - -export const INTERSTITIAL_METHOD = 'GET'; -const SERVER_API_URL = 'https://api.clerk.dev'; -const INTERSTITIAL_path = 'v1/internal/interstitial'; -const INTERSTITIAL_URL = `${SERVER_API_URL}/${INTERSTITIAL_path}`; -const API_CONTENT_TYPE = 'application/x-www-form-urlencoded'; - -type RequestOptions = { - method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT'; - path: string; - queryParams?: object; - bodyParams?: object; -}; - -/* - * HTTP fetch implementation (not specific to window.fetch API). - * The fetcher implementation should return the response body, not the whole response. - */ -export type ClerkFetcher = ( - url: string, - options: { - method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT'; - authorization: string; - contentType: string; - userAgent: string; - body?: Record; - }, -) => Promise; - -export default class RestClient { - apiKey: string; - serverApiUrl: string; - apiVersion: string; - fetcher: ClerkFetcher; - userAgent: string; - - constructor( - apiKey: string, - serverApiUrl: string, - apiVersion: string, - fetcher: ClerkFetcher, - libName: string, - libVersion: string, - packageRepo: string, - ) { - this.apiKey = apiKey; - this.serverApiUrl = serverApiUrl; - this.apiVersion = apiVersion; - this.fetcher = fetcher; - this.userAgent = `${libName}/${libVersion} (${packageRepo})`; - } - - makeRequest(requestOptions: RequestOptions): Promise { - let url = `${this.serverApiUrl}/${this.apiVersion}${requestOptions.path}`; - - if (requestOptions.queryParams) { - url = `${url}?${querystring.stringify(snakecaseKeys(requestOptions.queryParams))}`; - } - - let body; - if (requestOptions.bodyParams) { - body = snakecaseKeys(requestOptions.bodyParams) as Record; - } - - // TODO improve error handling - // TODO Instruct that data should be a JSON response - return this.fetcher(url, { - method: requestOptions.method, - authorization: `Bearer ${this.apiKey}`, - contentType: API_CONTENT_TYPE, - userAgent: this.userAgent, - body, - }) - .then(responseData => deserialize(responseData) as T) - .catch(error => handleError(error)); - } - - fetchInterstitial(): Promise { - return this.fetcher(INTERSTITIAL_URL, { - method: INTERSTITIAL_METHOD, - authorization: `Bearer ${this.apiKey}`, - contentType: 'text/html', - userAgent: this.userAgent, - }).catch(error => handleError(error)) as Promise; - } -} diff --git a/packages/backend-core/src/index.ts b/packages/backend-core/src/index.ts index 319d8773ad1..d28900e5288 100644 --- a/packages/backend-core/src/index.ts +++ b/packages/backend-core/src/index.ts @@ -1,8 +1,7 @@ +export * from './api'; export * from './Base'; -export * from './api/ClerkBackendAPI'; -export * from './api/resources'; +export * from './util/Logger'; + export { createGetToken, createSignedOutState } from './util/createGetToken'; export { AuthStatus, AuthErrorReason } from './types'; -export type { ClerkFetcher } from './api/utils/RestClient'; export type { Session } from './api/resources/Session'; -export type { Nullable } from './util/nullable'; diff --git a/packages/backend-core/src/api/utils/Logger.ts b/packages/backend-core/src/util/Logger.ts similarity index 96% rename from packages/backend-core/src/api/utils/Logger.ts rename to packages/backend-core/src/util/Logger.ts index f8cb1d52cd3..e003652c7ae 100644 --- a/packages/backend-core/src/api/utils/Logger.ts +++ b/packages/backend-core/src/util/Logger.ts @@ -14,7 +14,7 @@ type LogMessage = { message: string; }; -export default class Logger { +export class Logger { public static info(msg: string): void { Logger.log(LogLevel.Info, msg); } diff --git a/packages/backend-core/src/util/base64url.ts b/packages/backend-core/src/util/base64url.ts index 88f0ead6e4d..3fe7d728688 100644 --- a/packages/backend-core/src/util/base64url.ts +++ b/packages/backend-core/src/util/base64url.ts @@ -3,7 +3,7 @@ * Parse a string to base64 URL encoding. * * https://github.com/swansontec/rfc4648.js - * https://stackoverflow.com/questions/54062583/how-to-verify-a-signed-jwt-with-subtlecrypto-of-the-web-crypto-api + * https://stackoverflow.com/questions/54062583/how-to-verify-a-signed-jwt-with-subtlecrypto-of-the-web-crypto-API * * @param {string} input * @return {Uint8Array} out diff --git a/packages/backend-core/src/util/nullable.ts b/packages/backend-core/src/util/nullable.ts deleted file mode 100644 index 164a7056d97..00000000000 --- a/packages/backend-core/src/util/nullable.ts +++ /dev/null @@ -1 +0,0 @@ -export type Nullable = T | null | undefined; diff --git a/packages/backend-core/src/util/path.ts b/packages/backend-core/src/util/path.ts new file mode 100644 index 00000000000..708288bc14c --- /dev/null +++ b/packages/backend-core/src/util/path.ts @@ -0,0 +1,11 @@ +const SEPARATOR = '/'; +const MULTIPLE_SEPARATOR_REGEX = new RegExp(SEPARATOR + '{1,}', 'g'); + +type PathString = string | null | undefined; + +export function joinPaths(...args: PathString[]): string { + return args + .filter(p => p) + .join(SEPARATOR) + .replace(MULTIPLE_SEPARATOR_REGEX, SEPARATOR); +} diff --git a/packages/backend-core/tsconfig.json b/packages/backend-core/tsconfig.json index b6250342fd5..5baef6a8a86 100644 --- a/packages/backend-core/tsconfig.json +++ b/packages/backend-core/tsconfig.json @@ -18,5 +18,5 @@ "target": "ES2019" }, "include": ["src"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist", "src/**/*.spec.ts", "src/**/*.test.ts", "src/__tests__"] } diff --git a/packages/clerk-js/CHANGELOG.md b/packages/clerk-js/CHANGELOG.md index 3267a4db0cf..7dc8cbfca8b 100644 --- a/packages/clerk-js/CHANGELOG.md +++ b/packages/clerk-js/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.17.0](https://github.com/clerkinc/javascript/compare/@clerk/clerk-js@3.16.4...@clerk/clerk-js@3.17.0) (2022-08-04) + +### Features + +- **clerk-js:** Get support email from FAPI /v1/environment if exists ([c9bb8d7](https://github.com/clerkinc/javascript/commit/c9bb8d7aaf3958207d4799bdd30e3b15b2890a5d)) + ### [3.16.4](https://github.com/clerkinc/javascript/compare/@clerk/clerk-js@3.16.3...@clerk/clerk-js@3.16.4) (2022-07-13) **Note:** Version bump only for package @clerk/clerk-js diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index f334da2f9b0..6524cfe923a 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/clerk-js", - "version": "3.16.4", + "version": "3.17.0", "license": "MIT", "description": "Clerk.dev JS library", "keywords": [ @@ -39,7 +39,7 @@ "dev:v4": "webpack serve --config webpack.dev.v4.js" }, "dependencies": { - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "@emotion/cache": "^11.7.1", "@emotion/react": "^11.9.0", "@popperjs/core": "^2.4.4", @@ -61,7 +61,7 @@ "@babel/preset-env": "^7.12.1", "@babel/preset-react": "^7.12.5", "@babel/preset-typescript": "^7.12.1", - "@clerk/shared": "^0.3.6", + "@clerk/shared": "^0.3.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.6", "@svgr/webpack": "^6.2.1", "@testing-library/dom": "^7.28.1", diff --git a/packages/clerk-js/src/core/clerk.test.ts b/packages/clerk-js/src/core/clerk.test.ts index c8a40bdbfd5..f84563a761f 100644 --- a/packages/clerk-js/src/core/clerk.test.ts +++ b/packages/clerk-js/src/core/clerk.test.ts @@ -1,4 +1,4 @@ -import { SignInJSON, SignUpJSON } from '@clerk/types'; +import { ActiveSessionResource, SignInJSON, SignUpJSON } from '@clerk/types'; import { waitFor } from '@testing-library/dom'; import Clerk from 'core/clerk'; import { Client, DisplayConfig, Environment, MagicLinkErrorCode, SignIn, SignUp } from 'core/resources/internal'; @@ -39,6 +39,7 @@ describe('Clerk singleton', () => { } as DisplayConfig; let mockWindowLocation; + let mockWindowDocument; let mockHref: jest.Mock; beforeEach(() => { @@ -55,9 +56,8 @@ describe('Clerk singleton', () => { }, } as any; - Object.defineProperty(global.window, 'location', { - value: mockWindowLocation, - }); + Object.defineProperty(global.window, 'location', { value: mockWindowLocation }); + Object.defineProperty(global.window.document, 'hasFocus', { value: () => true, configurable: true }); // sut = new Clerk(frontendApi); }); @@ -127,6 +127,45 @@ describe('Clerk singleton', () => { }); }); + describe('.setActive', () => { + const mockSession = { + id: '1', + remove: jest.fn(), + status: 'active', + user: {}, + touch: jest.fn(), + }; + + beforeEach(() => { + mockSession.remove.mockReset(); + mockSession.touch.mockReset(); + }); + + it('calls session.touch by default', async () => { + mockSession.touch.mockReturnValueOnce(Promise.resolve()); + mockClientFetch.mockReturnValue(Promise.resolve({ activeSessions: [mockSession] })); + + const sut = new Clerk(frontendApi); + await sut.load(); + await sut.setActive({ session: mockSession as any as ActiveSessionResource }); + await waitFor(() => { + expect(mockSession.touch).toHaveBeenCalled(); + }); + }); + + it('does not call session.touch if Clerk was initialised with touchSession set to false', async () => { + mockSession.touch.mockReturnValueOnce(Promise.resolve()); + mockClientFetch.mockReturnValue(Promise.resolve({ activeSessions: [mockSession] })); + + const sut = new Clerk(frontendApi); + await sut.load({ touchSession: false }); + await sut.setActive({ session: mockSession as any as ActiveSessionResource }); + await waitFor(() => { + expect(mockSession.touch).not.toHaveBeenCalled(); + }); + }); + }); + describe('.load()', () => { it('gracefully handles an incorrect value returned from the user provided selectInitialSession', async () => { mockEnvironmentFetch.mockReturnValue( diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 6b864f325f5..d6aacbfb0cc 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -78,7 +78,7 @@ declare global { } } -const defaultOptions: ClerkOptions & { polling: boolean } = { polling: true }; +const defaultOptions: ClerkOptions = { polling: true, touchSession: true }; export default class Clerk implements ClerkInterface { public static Components?: typeof Components; @@ -857,7 +857,7 @@ export default class Clerk implements ClerkInterface { // TODO: Be more conservative about touches. Throttle, don't touch when only one user, etc #touchLastActiveSession = (session: ActiveSessionResource | null): Promise => { - if (!session) { + if (!session || !this.#options.touchSession) { return Promise.resolve(); } return session.touch().catch(noop); diff --git a/packages/clerk-js/src/core/errors.ts b/packages/clerk-js/src/core/errors.ts index 9cf7aab9fb6..1c5b85e6b93 100644 --- a/packages/clerk-js/src/core/errors.ts +++ b/packages/clerk-js/src/core/errors.ts @@ -16,7 +16,7 @@ export function clerkErrorDevInitFailed(msg?: string): never { export function clerkErrorPathRouterMissingPath(componentName: string): never { throw new Error( - `ClerkJS: Missing "path" option. The ${componentName} component was mounted with "path" routing but no "path" attribute was passed.`, + `ClerkJS: Missing path option. The ${componentName} component was mounted with path routing so you need to specify the path where the component is mounted on e.g. path="/sign-in".`, ); } diff --git a/packages/clerk-js/src/core/fapiClient.ts b/packages/clerk-js/src/core/fapiClient.ts index 67a1acec116..45866eaa8d3 100644 --- a/packages/clerk-js/src/core/fapiClient.ts +++ b/packages/clerk-js/src/core/fapiClient.ts @@ -26,7 +26,10 @@ export type FapiResponse = Response & { payload: FapiResponseJSON | null; }; -export type FapiRequestCallback = (request: FapiRequestInit, response?: FapiResponse) => Promise | void; +export type FapiRequestCallback = ( + request: FapiRequestInit, + response?: FapiResponse, +) => Promise | unknown | false; const camelToSnakeEncoder: qs.IStringifyOptions['encoder'] = (str, defaultEncoder, _, type) => { return type === 'key' ? camelToSnake(str) : defaultEncoder(str); @@ -68,15 +71,23 @@ export default function createFapiClient(clerkInstance: Clerk): FapiClient { } async function runBeforeRequestCallbacks(requestInit: FapiRequestInit) { - for await (const callback of onBeforeRequestCallbacks) { - await callback(requestInit); + const windowCallback = typeof window !== 'undefined' && (window as any).__unstable__onBeforeRequest; + for await (const callback of [windowCallback, ...onBeforeRequestCallbacks].filter(s => s)) { + if ((await callback(requestInit)) === false) { + return false; + } } + return true; } async function runAfterResponseCallbacks(requestInit: FapiRequestInit, response: FapiResponse) { - for await (const callback of onAfterResponseCallbacks) { - await callback(requestInit, response); + const windowCallback = typeof window !== 'undefined' && (window as any).__unstable__onAfterResponse; + for await (const callback of [windowCallback, ...onAfterResponseCallbacks].filter(s => s)) { + if ((await callback(requestInit, response)) === false) { + return false; + } } + return true; } function buildQueryString({ method, path, sessionId, search, rotatingTokenNonce }: FapiRequestInit) { @@ -151,35 +162,29 @@ export default function createFapiClient(clerkInstance: Clerk): FapiClient { requestInit.headers.set('Content-Type', 'application/x-www-form-urlencoded'); } - await runBeforeRequestCallbacks(requestInit); - + const beforeRequestCallbacksResult = await runBeforeRequestCallbacks(requestInit); // Due to a known Safari bug regarding CORS requests, we are forced to always use GET or POST method. // The original HTTP method is used as a query string parameter instead of as an actual method to // avoid triggering a CORS OPTION request as it currently breaks cookie dropping in Safari. const overwrittenRequestMethod = method === 'GET' ? 'GET' : 'POST'; - let response: Response; - const urlStr = requestInit.url.toString(); try { - response = await fetch(urlStr, { - ...requestInit, - credentials: 'include', - method: overwrittenRequestMethod, - }); + response = beforeRequestCallbacksResult + ? await fetch(urlStr, { + ...requestInit, + credentials: 'include', + method: overwrittenRequestMethod, + }) + : // Mock an empty json response + new Response('{}', requestInit); } catch (e) { clerkNetworkError(urlStr, e); } - const json: FapiResponseJSON = await response.json(); - - const fapiResponse: FapiResponse = Object.assign(response, { - payload: json, - }); - + const fapiResponse: FapiResponse = Object.assign(response, { payload: json }); await runAfterResponseCallbacks(requestInit, fapiResponse); - return fapiResponse; } diff --git a/packages/clerk-js/src/core/resources/DisplayConfig.ts b/packages/clerk-js/src/core/resources/DisplayConfig.ts index 329ce190db4..8e70fd1af07 100644 --- a/packages/clerk-js/src/core/resources/DisplayConfig.ts +++ b/packages/clerk-js/src/core/resources/DisplayConfig.ts @@ -10,24 +10,25 @@ import { BaseResource } from './internal'; export class DisplayConfig extends BaseResource implements DisplayConfigResource { id!: string; - instanceEnvironmentType!: string; + afterSignInUrl!: string; + afterSignOutAllUrl!: string; + afterSignOutOneUrl!: string; + afterSignOutUrl!: string; + afterSignUpUrl!: string; + afterSwitchSessionUrl!: string; applicationName!: string; - theme!: DisplayThemeJSON; - preferredSignInStrategy!: PreferredSignInStrategy; - logoImage!: ImageJSON; - faviconImage!: ImageJSON; backendHost!: string; + branded!: boolean; + faviconImage!: ImageJSON; homeUrl!: string; + instanceEnvironmentType!: string; + logoImage!: ImageJSON; + preferredSignInStrategy!: PreferredSignInStrategy; signInUrl!: string; signUpUrl!: string; + supportEmail!: string; + theme!: DisplayThemeJSON; userProfileUrl!: string; - afterSignInUrl!: string; - afterSignUpUrl!: string; - afterSignOutUrl!: string; - afterSignOutOneUrl!: string; - afterSignOutAllUrl!: string; - afterSwitchSessionUrl!: string; - branded!: boolean; public constructor(data: DisplayConfigJSON) { super(); @@ -54,6 +55,7 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource this.afterSignOutAllUrl = data.after_sign_out_all_url; this.afterSwitchSessionUrl = data.after_switch_session_url; this.branded = data.branded; + this.supportEmail = data.support_email || ''; return this; } } diff --git a/packages/clerk-js/src/core/services/authentication/AuthenticationService.ts b/packages/clerk-js/src/core/services/authentication/AuthenticationService.ts index 26f5cb80ae5..5f5440950e6 100644 --- a/packages/clerk-js/src/core/services/authentication/AuthenticationService.ts +++ b/packages/clerk-js/src/core/services/authentication/AuthenticationService.ts @@ -18,7 +18,7 @@ export class AuthenticationService { constructor(private clerk: Clerk) {} public initAuth = (opts: InitParams): void => { - this.enablePolling = opts.enablePolling || true; + this.enablePolling = opts.enablePolling ?? true; this.setAuthCookiesFromSession(this.clerk.session); this.setClientUatCookieForDevelopmentInstances(); this.clearLegacyAuthV1Cookies(); diff --git a/packages/clerk-js/src/ui/hooks/useSupportEmail.test.tsx b/packages/clerk-js/src/ui/hooks/useSupportEmail.test.tsx index 9f97117022a..5fa5eeccafd 100644 --- a/packages/clerk-js/src/ui/hooks/useSupportEmail.test.tsx +++ b/packages/clerk-js/src/ui/hooks/useSupportEmail.test.tsx @@ -1,6 +1,7 @@ import { renderHook } from '@clerk/shared/testUtils'; const mockUseOptions = jest.fn(); +const mockUseEnvironment = jest.fn(); import { useSupportEmail } from './useSupportEmail'; @@ -11,20 +12,31 @@ jest.mock('ui/contexts', () => { frontendApi: 'clerk.clerk.dev', }; }, + useEnvironment: mockUseEnvironment, useOptions: mockUseOptions, }; }); describe('useSupportEmail', () => { - test('should use custom email when provided', () => { + test('should use custom email when provided from options', () => { mockUseOptions.mockImplementationOnce(() => ({ supportEmail: 'test@email.com' })); + mockUseEnvironment.mockImplementationOnce(() => ({ displayConfig: { supportEmail: null } })); const { result } = renderHook(() => useSupportEmail()); expect(result.current).toBe('test@email.com'); }); - test('should fallback to default when supportEmail is not provided in options', () => { - mockUseOptions.mockImplementationOnce(() => ({ supportEmail: undefined })); + test('should use custom email when provided from the environment', () => { + mockUseOptions.mockImplementationOnce(() => ({})); + mockUseEnvironment.mockImplementationOnce(() => ({ displayConfig: { supportEmail: 'test@email.com' } })); + const { result } = renderHook(() => useSupportEmail()); + + expect(result.current).toBe('test@email.com'); + }); + + test('should fallback to default when supportEmail is not provided in options or the environment', () => { + mockUseOptions.mockImplementationOnce(() => ({})); + mockUseEnvironment.mockImplementationOnce(() => ({ displayConfig: { supportEmail: null } })); const { result } = renderHook(() => useSupportEmail()); expect(result.current).toBe('support@clerk.dev'); diff --git a/packages/clerk-js/src/ui/hooks/useSupportEmail.ts b/packages/clerk-js/src/ui/hooks/useSupportEmail.ts index f0b65047c0a..bcd04b6844e 100644 --- a/packages/clerk-js/src/ui/hooks/useSupportEmail.ts +++ b/packages/clerk-js/src/ui/hooks/useSupportEmail.ts @@ -1,20 +1,23 @@ import React from 'react'; import { buildEmailAddress } from '../../utils'; -import { useCoreClerk, useOptions } from '../contexts'; +import { useCoreClerk, useEnvironment, useOptions } from '../contexts'; export function useSupportEmail(): string { const Clerk = useCoreClerk(); - const { supportEmail } = useOptions(); + const { supportEmail: supportEmailFromOptions } = useOptions(); + const { displayConfig } = useEnvironment(); + const { supportEmail: supportEmailFromEnvironment } = displayConfig; const supportDomain = React.useMemo( () => - supportEmail || + supportEmailFromOptions || + supportEmailFromEnvironment || buildEmailAddress({ localPart: 'support', frontendApi: Clerk.frontendApi, }), - [Clerk.frontendApi, supportEmail], + [Clerk.frontendApi, supportEmailFromOptions, supportEmailFromEnvironment], ); return supportDomain; diff --git a/packages/edge/CHANGELOG.md b/packages/edge/CHANGELOG.md index 9f926e35ee5..bc61fd2398f 100644 --- a/packages/edge/CHANGELOG.md +++ b/packages/edge/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [1.7.1](https://github.com/clerkinc/javascript/compare/@clerk/edge@1.7.0...@clerk/edge@1.7.1) (2022-08-04) + +**Note:** Version bump only for package @clerk/edge + +## [1.7.0](https://github.com/clerkinc/javascript/compare/@clerk/edge@1.7.0-staging.0...@clerk/edge@1.7.0) (2022-07-26) + +**Note:** Version bump only for package @clerk/edge + ### [1.6.1](https://github.com/clerkinc/javascript/compare/@clerk/edge@1.6.0...@clerk/edge@1.6.1) (2022-07-13) **Note:** Version bump only for package @clerk/edge diff --git a/packages/edge/package.json b/packages/edge/package.json index 848e4a3cc14..c60daca93f9 100644 --- a/packages/edge/package.json +++ b/packages/edge/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/edge", - "version": "1.6.1", + "version": "1.7.1", "license": "MIT", "description": "Clerk SDK for serverless and edge environments", "keywords": [ @@ -36,8 +36,8 @@ "build": "node ./scripts/info.cjs && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && ./moduleTypeFix" }, "dependencies": { - "@clerk/backend-core": "^1.13.0", - "@clerk/types": "^2.20.0", + "@clerk/backend-core": "^1.14.1", + "@clerk/types": "^2.21.0", "@peculiar/webcrypto": "^1.2.3", "next": "^12.2.0" }, diff --git a/packages/edge/src/constants.ts b/packages/edge/src/constants.ts deleted file mode 100644 index 2432f64729a..00000000000 --- a/packages/edge/src/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { LIB_NAME, LIB_VERSION } from './info'; - -const SERVER_API_URL = 'https://api.clerk.dev'; -const apiVersion = 'v1'; -const INTERSTITIAL_path = '/internal/interstitial'; - -export const INTERSTITIAL_METHOD = 'GET'; -export const PACKAGE_REPO = 'https://github.com/clerkinc/clerk-sdk-edge'; -export const INTERSTITIAL_URL = `${SERVER_API_URL}/${apiVersion}${INTERSTITIAL_path}`; -export const SDK_USER_AGENT = `${LIB_NAME}/${LIB_VERSION} (${PACKAGE_REPO})`; diff --git a/packages/edge/src/info.ts b/packages/edge/src/info.ts index f7f9be29137..e7176b1e787 100644 --- a/packages/edge/src/info.ts +++ b/packages/edge/src/info.ts @@ -1,3 +1,3 @@ /** DO NOT EDIT: This file is automatically generated by ../scripts/info.js */ -export const LIB_VERSION = '1.6.1'; +export const LIB_VERSION = '1.7.1'; export const LIB_NAME = '@clerk/edge'; diff --git a/packages/edge/src/vercel-edge/ClerkAPI.ts b/packages/edge/src/vercel-edge/ClerkAPI.ts index 24041035ff1..2c0fdbd2a4c 100644 --- a/packages/edge/src/vercel-edge/ClerkAPI.ts +++ b/packages/edge/src/vercel-edge/ClerkAPI.ts @@ -1,22 +1,46 @@ -import { ClerkBackendAPI } from '@clerk/backend-core'; +import { ClerkAPIResponseError, ClerkBackendAPI } from '@clerk/backend-core'; -import { PACKAGE_REPO } from '../constants'; import { LIB_NAME, LIB_VERSION } from '../info'; export const ClerkAPI = new ClerkBackendAPI({ libName: LIB_NAME, libVersion: LIB_VERSION, - packageRepo: PACKAGE_REPO, - fetcher: (url, { method, authorization, contentType, userAgent, body }) => { - return fetch(url, { - method, - headers: { - authorization: authorization, - 'Content-Type': contentType, - 'User-Agent': userAgent, - 'X-Clerk-SDK': `vercel-edge/${LIB_VERSION}`, - }, - ...(body && { body: JSON.stringify(body) }), - }).then(body => (contentType === 'text/html' ? body : body.json())); + apiClient: { + async request({ url, method, queryParams, headerParams, bodyParams }) { + // Build final URL with search parameters + const finalUrl = new URL(url || ''); + + if (queryParams) { + for (const [key, val] of Object.entries(queryParams as Record)) { + // Support array values for queryParams such as { foo: [42, 43] } + if (val) { + [val].flat().forEach(v => finalUrl.searchParams.append(key, v)); + } + } + } + + const response = await fetch(finalUrl.href, { + method, + headers: { + ...(headerParams as Record), + 'X-Clerk-SDK': `vercel-edge/${LIB_VERSION}`, + }, + ...(bodyParams && Object.keys(bodyParams).length > 0 && { body: JSON.stringify(bodyParams) }), + }); + + // Parse JSON or Text response. + const isJSONResponse = headerParams && headerParams['Content-Type'] === 'application/json'; + const data = await (isJSONResponse ? response.json() : response.text()); + + // Check for errors + if (!response.ok) { + throw new ClerkAPIResponseError(response.statusText, { + data: data?.errors || data, + status: response.status, + }); + } + + return data; + }, }, }); diff --git a/packages/edge/src/vercel-edge/index.ts b/packages/edge/src/vercel-edge/index.ts index 29b81d668b6..98a8fca0078 100644 --- a/packages/edge/src/vercel-edge/index.ts +++ b/packages/edge/src/vercel-edge/index.ts @@ -9,7 +9,7 @@ import { WithEdgeMiddlewareAuthMiddlewareResult, WithEdgeMiddlewareAuthOptions, } from './types'; -import { injectAuthIntoRequest } from './utils'; +import { injectAuthIntoRequest } from './utils/injectAuthIntoRequest'; import { interstitialResponse, signedOutResponse } from './utils/responses'; /** @@ -56,8 +56,7 @@ const users = ClerkAPI.users; export { allowlistIdentifiers, clients, emails, invitations, organizations, sessions, smsMessages, users }; async function fetchInterstitial() { - const response = await ClerkAPI.fetchInterstitial(); - return response.text(); + return ClerkAPI.fetchInterstitial(); } export function withEdgeMiddlewareAuth< diff --git a/packages/edge/src/vercel-edge/utils/index.ts b/packages/edge/src/vercel-edge/utils/index.ts deleted file mode 100644 index 801966fa275..00000000000 --- a/packages/edge/src/vercel-edge/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './injectAuthIntoRequest'; diff --git a/packages/expo/CHANGELOG.md b/packages/expo/CHANGELOG.md index c5a9590bcca..d093ae81e53 100644 --- a/packages/expo/CHANGELOG.md +++ b/packages/expo/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [0.9.37](https://github.com/clerkinc/javascript/compare/@clerk/clerk-expo@0.9.36...@clerk/clerk-expo@0.9.37) (2022-08-04) + +**Note:** Version bump only for package @clerk/clerk-expo + ### [0.9.36](https://github.com/clerkinc/javascript/compare/@clerk/clerk-expo@0.9.35...@clerk/clerk-expo@0.9.36) (2022-07-13) **Note:** Version bump only for package @clerk/clerk-expo diff --git a/packages/expo/package.json b/packages/expo/package.json index ffd3d358538..44b76ccf47c 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/clerk-expo", - "version": "0.9.36", + "version": "0.9.37", "license": "MIT", "description": "Clerk.dev React Native/Expo library", "keywords": [ @@ -26,12 +26,12 @@ "dev": "tsc -p tsconfig.build.json --watch" }, "dependencies": { - "@clerk/clerk-js": "^3.16.4", - "@clerk/clerk-react": "^3.5.0", + "@clerk/clerk-js": "^3.17.0", + "@clerk/clerk-react": "^3.5.1", "base-64": "^1.0.0" }, "devDependencies": { - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "@types/jest": "^27.4.0", "@types/node": "^16.11.9", "@types/react": "^17.0.39", diff --git a/packages/nextjs/CHANGELOG.md b/packages/nextjs/CHANGELOG.md index 96e415ea6a0..07b59b3654e 100644 --- a/packages/nextjs/CHANGELOG.md +++ b/packages/nextjs/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [3.8.2](https://github.com/clerkinc/javascript/compare/@clerk/nextjs@3.8.1...@clerk/nextjs@3.8.2) (2022-08-04) + +**Note:** Version bump only for package @clerk/nextjs + +### [3.8.1](https://github.com/clerkinc/javascript/compare/@clerk/nextjs@3.8.1-staging.0...@clerk/nextjs@3.8.1) (2022-07-26) + +**Note:** Version bump only for package @clerk/nextjs + ## [3.8.0](https://github.com/clerkinc/javascript/compare/@clerk/nextjs@3.7.1...@clerk/nextjs@3.8.0) (2022-07-13) ### Features diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 4018062b081..ac1b5512144 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/nextjs", - "version": "3.8.0", + "version": "3.8.2", "license": "MIT", "description": "Clerk.dev SDK for NextJS", "keywords": [ @@ -32,10 +32,10 @@ "dev": "tsc -p tsconfig.build.json --watch" }, "dependencies": { - "@clerk/clerk-react": "^3.5.0", - "@clerk/clerk-sdk-node": "^3.8.6", - "@clerk/edge": "^1.6.1", - "@clerk/types": "^2.20.0", + "@clerk/clerk-react": "^3.5.1", + "@clerk/clerk-sdk-node": "^3.9.1", + "@clerk/edge": "^1.7.1", + "@clerk/types": "^2.21.0", "tslib": "^2.3.1" }, "devDependencies": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 26b74b8b82a..85e40577059 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [3.5.1](https://github.com/clerkinc/javascript/compare/@clerk/clerk-react@3.5.0...@clerk/clerk-react@3.5.1) (2022-08-04) + +**Note:** Version bump only for package @clerk/clerk-react + ## [3.5.0](https://github.com/clerkinc/javascript/compare/@clerk/clerk-react@3.4.5...@clerk/clerk-react@3.5.0) (2022-07-13) ### Features diff --git a/packages/react/package.json b/packages/react/package.json index a20ad652c9f..952a3781054 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/clerk-react", - "version": "3.5.0", + "version": "3.5.1", "license": "MIT", "description": "Clerk.dev React library", "keywords": [ @@ -28,7 +28,7 @@ "test": "jest" }, "dependencies": { - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "swr": "^1.3.0", "tslib": "^2.3.1" }, diff --git a/packages/react/src/info.ts b/packages/react/src/info.ts index 7a3490efb30..c4de1df3f9c 100644 --- a/packages/react/src/info.ts +++ b/packages/react/src/info.ts @@ -1,3 +1,3 @@ /** DO NOT EDIT: This file is automatically generated by ../scripts/info.js */ -export const LIB_VERSION = '3.5.0'; +export const LIB_VERSION = '3.5.1'; export const LIB_NAME = '@clerk/clerk-react'; diff --git a/packages/remix/CHANGELOG.md b/packages/remix/CHANGELOG.md index ae4e59e40f5..68ff87ea82d 100644 --- a/packages/remix/CHANGELOG.md +++ b/packages/remix/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [0.5.13](https://github.com/clerkinc/javascript/compare/@clerk/remix@0.5.12...@clerk/remix@0.5.13) (2022-08-04) + +**Note:** Version bump only for package @clerk/remix + +### [0.5.12](https://github.com/clerkinc/javascript/compare/@clerk/remix@0.5.12-staging.0...@clerk/remix@0.5.12) (2022-07-26) + +**Note:** Version bump only for package @clerk/remix + ### [0.5.11](https://github.com/clerkinc/javascript/compare/@clerk/remix@0.5.10...@clerk/remix@0.5.11) (2022-07-13) **Note:** Version bump only for package @clerk/remix diff --git a/packages/remix/package.json b/packages/remix/package.json index 9f91e935e7c..f2401dc626d 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/remix", - "version": "0.5.11", + "version": "0.5.13", "license": "MIT", "description": "Clerk.dev SDK for Remix", "keywords": [ @@ -32,9 +32,9 @@ "dev": "tsc -p tsconfig.build.json --watch" }, "dependencies": { - "@clerk/clerk-react": "^3.5.0", - "@clerk/clerk-sdk-node": "^3.8.6", - "@clerk/types": "^2.20.0", + "@clerk/clerk-react": "^3.5.1", + "@clerk/clerk-sdk-node": "^3.9.1", + "@clerk/types": "^2.21.0", "cookie": "^0.5.0", "tslib": "^2.3.1" }, diff --git a/packages/sdk-node/CHANGELOG.md b/packages/sdk-node/CHANGELOG.md index d5d7da9beeb..1e385466315 100644 --- a/packages/sdk-node/CHANGELOG.md +++ b/packages/sdk-node/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [3.9.1](https://github.com/clerkinc/javascript/compare/@clerk/clerk-sdk-node@3.9.0...@clerk/clerk-sdk-node@3.9.1) (2022-08-04) + +**Note:** Version bump only for package @clerk/clerk-sdk-node + +## [3.9.0](https://github.com/clerkinc/javascript/compare/@clerk/clerk-sdk-node@3.9.0-staging.0...@clerk/clerk-sdk-node@3.9.0) (2022-07-26) + +**Note:** Version bump only for package @clerk/clerk-sdk-node + ### [3.8.6](https://github.com/clerkinc/javascript/compare/@clerk/clerk-sdk-node@3.8.5...@clerk/clerk-sdk-node@3.8.6) (2022-07-13) **Note:** Version bump only for package @clerk/clerk-sdk-node diff --git a/packages/sdk-node/examples/node/package-lock.json b/packages/sdk-node/examples/node/package-lock.json index 4c71c41e11d..58c7d8f3239 100644 --- a/packages/sdk-node/examples/node/package-lock.json +++ b/packages/sdk-node/examples/node/package-lock.json @@ -1,396 +1,396 @@ { - "name": "clerk-sdk-node-examples", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "clerk-sdk-node-examples", - "license": "MIT", - "dependencies": { - "@clerk/clerk-sdk-node": "file:../../dist", - "dotenv": "^8.6.0", - "log-that-http": "^1.0.1" - }, - "devDependencies": { - "@types/react-dom": "^17.0.1", - "ts-node": "^10.4.0", - "typescript": "^4.4.4" - } - }, - "../../dist": {}, - "node_modules/@clerk/clerk-sdk-node": { - "resolved": "../../dist", - "link": true - }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, - "node_modules/@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "dev": true - }, - "node_modules/@types/react": { - "version": "17.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", - "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", - "dev": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", - "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", - "dev": true - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "engines": { - "node": ">=10" - } - }, - "node_modules/log-that-http": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/log-that-http/-/log-that-http-1.0.1.tgz", - "integrity": "sha512-m382CmTTsDdf288oUdQdYMBDUisiQgXkqIIPw8stFqjSZwjgrg4ap7S3viniy7qv3KTnv3CCb/GFQWAuqLJu3Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/ts-node": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", - "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - } + "name": "clerk-sdk-node-examples", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "clerk-sdk-node-examples", + "license": "MIT", + "dependencies": { + "@clerk/clerk-sdk-node": "file:../../dist", + "dotenv": "^8.6.0", + "log-that-http": "^1.0.1" + }, + "devDependencies": { + "@types/react-dom": "^17.0.1", + "ts-node": "^10.4.0", + "typescript": "^4.4.4" + } }, - "dependencies": { - "@clerk/clerk-sdk-node": { - "version": "file:../../dist" - }, - "@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true - }, - "@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", - "dev": true, - "requires": { - "@cspotcode/source-map-consumer": "0.8.0" - } - }, - "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, - "@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "dev": true - }, - "@types/react": { - "version": "17.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", - "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", - "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true - }, - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" - }, - "log-that-http": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/log-that-http/-/log-that-http-1.0.1.tgz", - "integrity": "sha512-m382CmTTsDdf288oUdQdYMBDUisiQgXkqIIPw8stFqjSZwjgrg4ap7S3viniy7qv3KTnv3CCb/GFQWAuqLJu3Q==" - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "ts-node": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", - "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "yn": "3.1.1" - } - }, - "typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", - "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true + "../../dist": {}, + "node_modules/@clerk/clerk-sdk-node": { + "resolved": "../../dist", + "link": true + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", + "dev": true + }, + "node_modules/@types/react": { + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", + "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", + "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/log-that-http": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/log-that-http/-/log-that-http-1.0.1.tgz", + "integrity": "sha512-m382CmTTsDdf288oUdQdYMBDUisiQgXkqIIPw8stFqjSZwjgrg4ap7S3viniy7qv3KTnv3CCb/GFQWAuqLJu3Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true } + } + }, + "node_modules/typescript": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@clerk/clerk-sdk-node": { + "version": "file:../../dist" + }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", + "dev": true + }, + "@types/react": { + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", + "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", + "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "csstype": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" + }, + "log-that-http": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/log-that-http/-/log-that-http-1.0.1.tgz", + "integrity": "sha512-m382CmTTsDdf288oUdQdYMBDUisiQgXkqIIPw8stFqjSZwjgrg4ap7S3viniy7qv3KTnv3CCb/GFQWAuqLJu3Q==" + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } + } } diff --git a/packages/sdk-node/examples/node/src/organizations.ts b/packages/sdk-node/examples/node/src/organizations.ts new file mode 100644 index 00000000000..3661d2f5c47 --- /dev/null +++ b/packages/sdk-node/examples/node/src/organizations.ts @@ -0,0 +1,18 @@ +import { organizations, users } from '@clerk/clerk-sdk-node'; + +console.log('Get user to create organization'); +const [creator] = await users.getUserList(); + +console.log('Create organization'); +const organization = await organizations.createOrganization({ + name: 'test-organization', + createdBy: creator.id, +}); +console.log(organization); + +console.log('Update organization metadata'); +const updatedOrganizationMetadata = + await organizations.updateOrganizationMetadata(organization.id, { + publicMetadata: { test: 1 }, + }); +console.log(updatedOrganizationMetadata); diff --git a/packages/sdk-node/package.json b/packages/sdk-node/package.json index ce8c4782f28..bb976ec25ae 100644 --- a/packages/sdk-node/package.json +++ b/packages/sdk-node/package.json @@ -1,5 +1,5 @@ { - "version": "3.8.6", + "version": "3.9.1", "license": "MIT", "main": "dist/index.js", "module": "esm/index.js", @@ -12,7 +12,7 @@ "package.json" ], "engines": { - "node": ">=12" + "node": ">=14" }, "scripts": { "build:es5": "node ./scripts/info.js && tsc -p tsconfig.build.json", @@ -39,6 +39,7 @@ "@types/express": "^4.17.11", "@types/jest": "^27.4.0", "@types/jsonwebtoken": "^8.5.6", + "@types/node-fetch": "^2", "jest": "^27.4.7", "nock": "^13.0.7", "npm-run-all": "^4.1.5", @@ -47,15 +48,15 @@ "typescript": "^4.6.4" }, "dependencies": { - "@clerk/backend-core": "^1.13.0", - "@clerk/types": "^2.20.0", + "@clerk/backend-core": "^1.14.1", + "@clerk/types": "^2.21.0", "@peculiar/webcrypto": "^1.2.3", "camelcase-keys": "^6.2.2", "cookies": "^0.8.0", "deepmerge": "^4.2.2", - "got": "^11.8.2", "jsonwebtoken": "^8.5.1", "jwks-rsa": "^2.0.4", + "node-fetch": "^2.6.0", "snakecase-keys": "^3.2.1", "tslib": "^2.3.1" }, diff --git a/packages/sdk-node/src/Clerk.ts b/packages/sdk-node/src/Clerk.ts index d47dace83c4..c6adbdb0967 100644 --- a/packages/sdk-node/src/Clerk.ts +++ b/packages/sdk-node/src/Clerk.ts @@ -1,27 +1,29 @@ import { + APIRequestOptions, AuthStatus, Base, + ClerkAPIResponseError, ClerkBackendAPI, - ClerkFetcher, createGetToken, createSignedOutState, + Logger, } from '@clerk/backend-core'; import { ClerkJWTClaims, ServerGetToken } from '@clerk/types'; -import { Crypto, CryptoKey } from '@peculiar/webcrypto'; import Cookies from 'cookies'; import deepmerge from 'deepmerge'; import type { NextFunction, Request, Response } from 'express'; -import got, { OptionsOfUnknownResponseBody } from 'got'; import jwt from 'jsonwebtoken'; import jwks, { JwksClient } from 'jwks-rsa'; -import querystring from 'querystring'; +import fetch, { RequestInit } from 'node-fetch'; import { SupportMessages } from './constants/SupportMessages'; import { LIB_NAME, LIB_VERSION } from './info'; -import { decodeBase64, toSPKIDer } from './utils/crypto'; -import { ClerkServerError } from './utils/Errors'; -// utils -import Logger from './utils/Logger'; +import { + decodeBase64, + importJSONWebKey, + importPKIKey, + verifySignature, +} from './utils'; const defaultApiKey = process.env.CLERK_API_KEY || ''; const defaultJWTKey = process.env.CLERK_JWT_KEY; @@ -29,13 +31,6 @@ const defaultApiVersion = process.env.CLERK_API_VERSION || 'v1'; const defaultServerApiUrl = process.env.CLERK_API_URL || 'https://api.clerk.dev'; const JWKS_MAX_AGE = 3600000; // 1 hour -const packageRepo = 'https://github.com/clerkinc/clerk-sdk-node'; - -export type MiddlewareOptions = { - onError?: Function; - authorizedParties?: string[]; - jwtKey?: string; -}; export type WithAuthProp = T & { auth: { @@ -55,25 +50,26 @@ export type RequireAuthProp = T & { }; }; -const crypto = new Crypto(); +export type Middleware = ( + req: WithAuthProp, + res: Response, + next: NextFunction +) => Promise; -const importKey = async (jwk: JsonWebKey, algorithm: Algorithm) => { - return await crypto.subtle.importKey('jwk', jwk, algorithm, true, ['verify']); +export type MiddlewareOptions = { + onError?: (error: ClerkAPIResponseError) => unknown; + authorizedParties?: string[]; + jwtKey?: string; + strict?: boolean; }; -const verifySignature = async ( - algorithm: Algorithm, - key: CryptoKey, - signature: Uint8Array, - data: Uint8Array -) => { - return await crypto.subtle.verify(algorithm, key, signature, data); -}; +// eslint-disable-next-line @typescript-eslint/no-empty-function +const noop: NextFunction = () => {}; export default class Clerk extends ClerkBackendAPI { base: Base; jwtKey?: string; - httpOptions: OptionsOfUnknownResponseBody; + httpOptions: RequestInit; _jwksClient: JwksClient; @@ -83,46 +79,74 @@ export default class Clerk extends ClerkBackendAPI { constructor({ apiKey = defaultApiKey, jwtKey = defaultJWTKey, - serverApiUrl = defaultServerApiUrl, + apiUrl = defaultServerApiUrl, apiVersion = defaultApiVersion, httpOptions = {}, jwksCacheMaxAge = JWKS_MAX_AGE, }: { apiKey?: string; jwtKey?: string; - serverApiUrl?: string; + apiUrl?: string; apiVersion?: string; - httpOptions?: OptionsOfUnknownResponseBody; + httpOptions?: RequestInit; jwksCacheMaxAge?: number; } = {}) { - const fetcher: ClerkFetcher = ( - url, - { method, authorization, contentType, userAgent, body } - ) => { - const finalHTTPOptions = deepmerge(this.httpOptions, { - method, - responseType: contentType === 'text/html' ? 'text' : 'json', - headers: { - authorization, - 'Content-Type': contentType, - 'User-Agent': userAgent, - 'X-Clerk-SDK': `node/${LIB_VERSION}`, - }, - // @ts-ignore - ...(body && { body: querystring.stringify(body) }), - }) as OptionsOfUnknownResponseBody; - - return got(url, finalHTTPOptions).then((data) => data.body); - }; - super({ apiKey, apiVersion, - serverApiUrl, + apiUrl, libName: LIB_NAME, libVersion: LIB_VERSION, - packageRepo, - fetcher, + apiClient: { + async request(options: APIRequestOptions) { + const { url, method, queryParams, headerParams, bodyParams } = + options; + // Build final URL with search parameters + const finalUrl = new URL(url || ''); + + if (queryParams) { + for (const [key, val] of Object.entries( + queryParams as Record + )) { + // Support array values for queryParams such as { foo: [42, 43] } + if (val) { + [val] + .flat() + .forEach((v) => finalUrl.searchParams.append(key, v)); + } + } + } + + const response = await fetch( + finalUrl.href, + deepmerge(httpOptions, { + method, + headers: headerParams as Record, + ...(bodyParams && + Object.keys(bodyParams).length > 0 && { + body: JSON.stringify(bodyParams), + }), + }) + ); + + // Parse JSON or Text response. + const isJSONResponse = + headerParams && headerParams['Content-Type'] === 'application/json'; + const data = (await (isJSONResponse + ? response.json() + : response.text())) as T; + + // Check for errors + if (!response.ok) { + throw new ClerkAPIResponseError(response.statusText, { + data: (data as any)?.errors || data, + status: response.status, + }); + } + + return data; + }, + }, }); if (!apiKey) { @@ -133,7 +157,7 @@ export default class Clerk extends ClerkBackendAPI { this.jwtKey = jwtKey; this._jwksClient = jwks({ - jwksUri: `${serverApiUrl}/${apiVersion}/jwks`, + jwksUri: `${apiUrl}/${apiVersion}/jwks`, requestHeaders: { Authorization: `Bearer ${apiKey}`, }, @@ -151,25 +175,15 @@ export default class Clerk extends ClerkBackendAPI { const signingKey = await this._jwksClient.getSigningKey( decoded.header.kid ); - const pubKey = signingKey.getPublicKey(); - - return await crypto.subtle.importKey( - 'spki', - toSPKIDer(pubKey), - { - name: 'RSASSA-PKCS1-v1_5', - hash: 'SHA-256', - }, - true, - ['verify'] - ); + const publicKey = signingKey.getPublicKey(); + + return importPKIKey(publicKey); }; /** Base initialization */ - // TODO: More comprehensive base initialization this.base = new Base( - importKey, + importJSONWebKey, verifySignature, decodeBase64, loadCryptoKey @@ -216,37 +230,24 @@ export default class Clerk extends ClerkBackendAPI { return this._instance; } - // Middlewares - - // defaultOnError swallows the error - defaultOnError(error: Error & { data: any }) { - Logger.warn(error.message); + private logError(error: ClerkAPIResponseError | Error, strict = false) { + const logLevel = strict ? 'error' : 'warn'; + Logger[logLevel](error.message); - (error.data || []).forEach((serverError: ClerkServerError) => { - Logger.warn(serverError.longMessage); - }); - } - - // strictOnError returns the error so that Express will halt the request chain - strictOnError(error: Error & { data: any }) { - Logger.error(error.message); - - (error.data || []).forEach((serverError: ClerkServerError) => { - Logger.error(serverError.longMessage); - }); - - return error; + if (error instanceof ClerkAPIResponseError) { + (error.errors || []).forEach(({ message, longMessage }) => + Logger[logLevel](longMessage || message) + ); + } } - expressWithAuth( - { onError, authorizedParties, jwtKey }: MiddlewareOptions = { - onError: this.defaultOnError, - } - ): (req: Request, res: Response, next: NextFunction) => Promise { + private nodeAuthenticate(options: MiddlewareOptions): Middleware { function signedOut() { throw new Error('Unauthenticated'); } + const { onError, authorizedParties, jwtKey, strict } = options; + async function authenticate( this: Clerk, req: Request, @@ -310,14 +311,11 @@ export default class Clerk extends ClerkBackendAPI { claims: null, }; - // Call onError if provided - if (!onError) { - return next(); - } - - const err = await onError(error); + this.logError(error as any, strict); + const err = onError ? await onError(error as any) : error; - if (err) { + // If strict, err will be returned to the Express-like `next` callback to signify an error should halt the request chain + if (strict) { next(err); } else { next(); @@ -328,14 +326,6 @@ export default class Clerk extends ClerkBackendAPI { return authenticate.bind(this); } - expressRequireAuth( - options: MiddlewareOptions = { - onError: this.strictOnError, - } - ) { - return this.expressWithAuth(options); - } - // Credits to https://nextjs.org/docs/api-routes/api-middlewares // Helper method to wait for a middleware to execute before continuing // And to throw an error when an error happens in a middleware @@ -353,13 +343,18 @@ export default class Clerk extends ClerkBackendAPI { }); } + // Middlewares + + expressWithAuth(options: MiddlewareOptions = {}) { + return this.nodeAuthenticate({ strict: false, ...options }); + } + + expressRequireAuth(options?: MiddlewareOptions) { + return this.nodeAuthenticate({ strict: true, ...options }); + } + // Set the session on the request and then call provided handler - withAuth( - handler: Function, - options: MiddlewareOptions = { - onError: this.defaultOnError, - } - ) { + withAuth(handler: Middleware, options?: MiddlewareOptions) { return async ( req: WithAuthProp, res: Response, @@ -367,7 +362,7 @@ export default class Clerk extends ClerkBackendAPI { ) => { try { await this._runMiddleware(req, res, this.expressWithAuth(options)); - return handler(req, res, next); + return handler(req, res, next || noop); } catch (error) { // @ts-ignore const errorData = error.data || { error: error.message }; @@ -384,12 +379,7 @@ export default class Clerk extends ClerkBackendAPI { } // Stricter version, short-circuits if session can't be determined - requireAuth( - handler: Function, - options: MiddlewareOptions = { - onError: this.strictOnError, - } - ) { - return this.withAuth(handler, options); + requireAuth(handler: Middleware, options?: MiddlewareOptions) { + return this.withAuth(handler, { strict: true, ...options }); } } diff --git a/packages/sdk-node/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/sdk-node/src/__tests__/__snapshots__/exports.test.ts.snap index d193d9832a5..04b21dd8ac2 100644 --- a/packages/sdk-node/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/sdk-node/src/__tests__/__snapshots__/exports.test.ts.snap @@ -5,15 +5,12 @@ Object { "AllowlistIdentifier": [Function], "ClerkExpressRequireAuth": [Function], "ClerkExpressWithAuth": [Function], - "ClerkServerError": [Function], "Client": [Function], "Email": [Function], "EmailAddress": [Function], "ExternalAccount": [Function], - "HttpError": [Function], "IdentificationLink": [Function], "Invitation": [Function], - "Logger": [Function], "Organization": [Function], "OrganizationInvitation": [Function], "OrganizationMembership": [Function], @@ -22,60 +19,170 @@ Object { "SMSMessage": [Function], "User": [Function], "Verification": [Function], - "allowlistIdentifiers": AllowlistIdentifierApi { - "_restClient": RestClient { + "allowlistIdentifiers": AllowlistIdentifierAPI { + "APIClient": Clerk { + "_allowlistIdentifierAPI": [Circular], + "_clientAPI": ClientAPI { + "APIClient": [Circular], + }, + "_emailAPI": EmailAPI { + "APIClient": [Circular], + }, + "_invitationAPI": InvitationAPI { + "APIClient": [Circular], + }, + "_jwksClient": JwksClient { + "getSigningKey": [Function], + "options": Object { + "cache": true, + "cacheMaxAge": 3600000, + "jwksUri": "https://api.clerk.dev/v1/jwks", + "rateLimit": false, + "requestHeaders": Object { + "Authorization": "Bearer TEST_API_KEY", + }, + "timeout": 5000, + }, + }, + "_organizationAPI": OrganizationAPI { + "APIClient": [Circular], + }, + "_sessionAPI": SessionAPI { + "APIClient": [Circular], + "getSession": [Function], + "getSessionList": [Function], + "getToken": [Function], + "revokeSession": [Function], + "verifySession": [Function], + }, + "_smsMessageAPI": SMSMessageAPI { + "APIClient": [Circular], + }, + "_userAPI": UserAPI { + "APIClient": [Circular], + }, + "apiClient": Object { + "request": [Function], + }, "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", + "base": Base { + "buildAuthenticatedState": [Function], + "decodeBase64Function": [Function], + "decodeJwt": [Function], + "getAuthState": [Function], + "importKeyFunction": [Function], + "loadCryptoKey": [Function], + "loadCryptoKeyFunction": [Function], + "verifyJwt": [Function], + "verifyJwtSignature": [Function], + "verifySessionToken": [Function], + "verifySignatureFunction": [Function], + }, + "httpOptions": Object {}, + "jwtKey": undefined, + "options": Object { + "apiClient": Object { + "request": [Function], + }, + "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", + "apiVersion": "v1", + "libName": "@clerk/clerk-sdk-node", + "libVersion": "3.7.0", + }, + "userAgent": "@clerk/clerk-sdk-node@3.7.0", }, }, - "clients": ClientApi { - "_restClient": RestClient { + "clients": ClientAPI { + "APIClient": Clerk { + "_allowlistIdentifierAPI": AllowlistIdentifierAPI { + "APIClient": [Circular], + }, + "_clientAPI": [Circular], + "_emailAPI": EmailAPI { + "APIClient": [Circular], + }, + "_invitationAPI": InvitationAPI { + "APIClient": [Circular], + }, + "_jwksClient": JwksClient { + "getSigningKey": [Function], + "options": Object { + "cache": true, + "cacheMaxAge": 3600000, + "jwksUri": "https://api.clerk.dev/v1/jwks", + "rateLimit": false, + "requestHeaders": Object { + "Authorization": "Bearer TEST_API_KEY", + }, + "timeout": 5000, + }, + }, + "_organizationAPI": OrganizationAPI { + "APIClient": [Circular], + }, + "_sessionAPI": SessionAPI { + "APIClient": [Circular], + "getSession": [Function], + "getSessionList": [Function], + "getToken": [Function], + "revokeSession": [Function], + "verifySession": [Function], + }, + "_smsMessageAPI": SMSMessageAPI { + "APIClient": [Circular], + }, + "_userAPI": UserAPI { + "APIClient": [Circular], + }, + "apiClient": Object { + "request": [Function], + }, "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", - }, - }, - "default": Clerk { - "_allowlistIdentifierApi": AllowlistIdentifierApi { - "_restClient": RestClient { - "apiKey": "TEST_API_KEY", - "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", + "base": Base { + "buildAuthenticatedState": [Function], + "decodeBase64Function": [Function], + "decodeJwt": [Function], + "getAuthState": [Function], + "importKeyFunction": [Function], + "loadCryptoKey": [Function], + "loadCryptoKeyFunction": [Function], + "verifyJwt": [Function], + "verifyJwtSignature": [Function], + "verifySessionToken": [Function], + "verifySignatureFunction": [Function], }, - }, - "_clientApi": ClientApi { - "_restClient": RestClient { + "httpOptions": Object {}, + "jwtKey": undefined, + "options": Object { + "apiClient": Object { + "request": [Function], + }, "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", + "libName": "@clerk/clerk-sdk-node", + "libVersion": "3.7.0", }, + "userAgent": "@clerk/clerk-sdk-node@3.7.0", }, - "_emailApi": EmailApi { - "_restClient": RestClient { - "apiKey": "TEST_API_KEY", - "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", - }, + }, + "default": Clerk { + "_allowlistIdentifierAPI": AllowlistIdentifierAPI { + "APIClient": [Circular], }, - "_invitationApi": InvitationApi { - "_restClient": RestClient { - "apiKey": "TEST_API_KEY", - "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", - }, + "_clientAPI": ClientAPI { + "APIClient": [Circular], + }, + "_emailAPI": EmailAPI { + "APIClient": [Circular], + }, + "_invitationAPI": InvitationAPI { + "APIClient": [Circular], }, "_jwksClient": JwksClient { "getSigningKey": [Function], @@ -90,54 +197,29 @@ Object { "timeout": 5000, }, }, - "_organizationApi": OrganizationApi { - "_restClient": RestClient { - "apiKey": "TEST_API_KEY", - "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", - }, - }, - "_restClient": RestClient { - "apiKey": "TEST_API_KEY", - "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", + "_organizationAPI": OrganizationAPI { + "APIClient": [Circular], }, - "_sessionApi": SessionApi { - "_restClient": RestClient { - "apiKey": "TEST_API_KEY", - "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", - }, + "_sessionAPI": SessionAPI { + "APIClient": [Circular], "getSession": [Function], "getSessionList": [Function], "getToken": [Function], "revokeSession": [Function], "verifySession": [Function], }, - "_smsMessageApi": SMSMessageApi { - "_restClient": RestClient { - "apiKey": "TEST_API_KEY", - "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", - }, + "_smsMessageAPI": SMSMessageAPI { + "APIClient": [Circular], }, - "_userApi": UserApi { - "_restClient": RestClient { - "apiKey": "TEST_API_KEY", - "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", - }, + "_userAPI": UserAPI { + "APIClient": [Circular], + }, + "apiClient": Object { + "request": [Function], }, + "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", + "apiVersion": "v1", "base": Base { "buildAuthenticatedState": [Function], "decodeBase64Function": [Function], @@ -153,42 +235,316 @@ Object { }, "httpOptions": Object {}, "jwtKey": undefined, + "options": Object { + "apiClient": Object { + "request": [Function], + }, + "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", + "apiVersion": "v1", + "libName": "@clerk/clerk-sdk-node", + "libVersion": "3.7.0", + }, + "userAgent": "@clerk/clerk-sdk-node@3.7.0", }, - "emails": EmailApi { - "_restClient": RestClient { + "emails": EmailAPI { + "APIClient": Clerk { + "_allowlistIdentifierAPI": AllowlistIdentifierAPI { + "APIClient": [Circular], + }, + "_clientAPI": ClientAPI { + "APIClient": [Circular], + }, + "_emailAPI": [Circular], + "_invitationAPI": InvitationAPI { + "APIClient": [Circular], + }, + "_jwksClient": JwksClient { + "getSigningKey": [Function], + "options": Object { + "cache": true, + "cacheMaxAge": 3600000, + "jwksUri": "https://api.clerk.dev/v1/jwks", + "rateLimit": false, + "requestHeaders": Object { + "Authorization": "Bearer TEST_API_KEY", + }, + "timeout": 5000, + }, + }, + "_organizationAPI": OrganizationAPI { + "APIClient": [Circular], + }, + "_sessionAPI": SessionAPI { + "APIClient": [Circular], + "getSession": [Function], + "getSessionList": [Function], + "getToken": [Function], + "revokeSession": [Function], + "verifySession": [Function], + }, + "_smsMessageAPI": SMSMessageAPI { + "APIClient": [Circular], + }, + "_userAPI": UserAPI { + "APIClient": [Circular], + }, + "apiClient": Object { + "request": [Function], + }, "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", + "base": Base { + "buildAuthenticatedState": [Function], + "decodeBase64Function": [Function], + "decodeJwt": [Function], + "getAuthState": [Function], + "importKeyFunction": [Function], + "loadCryptoKey": [Function], + "loadCryptoKeyFunction": [Function], + "verifyJwt": [Function], + "verifyJwtSignature": [Function], + "verifySessionToken": [Function], + "verifySignatureFunction": [Function], + }, + "httpOptions": Object {}, + "jwtKey": undefined, + "options": Object { + "apiClient": Object { + "request": [Function], + }, + "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", + "apiVersion": "v1", + "libName": "@clerk/clerk-sdk-node", + "libVersion": "3.7.0", + }, + "userAgent": "@clerk/clerk-sdk-node@3.7.0", }, }, - "invitations": InvitationApi { - "_restClient": RestClient { + "invitations": InvitationAPI { + "APIClient": Clerk { + "_allowlistIdentifierAPI": AllowlistIdentifierAPI { + "APIClient": [Circular], + }, + "_clientAPI": ClientAPI { + "APIClient": [Circular], + }, + "_emailAPI": EmailAPI { + "APIClient": [Circular], + }, + "_invitationAPI": [Circular], + "_jwksClient": JwksClient { + "getSigningKey": [Function], + "options": Object { + "cache": true, + "cacheMaxAge": 3600000, + "jwksUri": "https://api.clerk.dev/v1/jwks", + "rateLimit": false, + "requestHeaders": Object { + "Authorization": "Bearer TEST_API_KEY", + }, + "timeout": 5000, + }, + }, + "_organizationAPI": OrganizationAPI { + "APIClient": [Circular], + }, + "_sessionAPI": SessionAPI { + "APIClient": [Circular], + "getSession": [Function], + "getSessionList": [Function], + "getToken": [Function], + "revokeSession": [Function], + "verifySession": [Function], + }, + "_smsMessageAPI": SMSMessageAPI { + "APIClient": [Circular], + }, + "_userAPI": UserAPI { + "APIClient": [Circular], + }, + "apiClient": Object { + "request": [Function], + }, "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", + "base": Base { + "buildAuthenticatedState": [Function], + "decodeBase64Function": [Function], + "decodeJwt": [Function], + "getAuthState": [Function], + "importKeyFunction": [Function], + "loadCryptoKey": [Function], + "loadCryptoKeyFunction": [Function], + "verifyJwt": [Function], + "verifyJwtSignature": [Function], + "verifySessionToken": [Function], + "verifySignatureFunction": [Function], + }, + "httpOptions": Object {}, + "jwtKey": undefined, + "options": Object { + "apiClient": Object { + "request": [Function], + }, + "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", + "apiVersion": "v1", + "libName": "@clerk/clerk-sdk-node", + "libVersion": "3.7.0", + }, + "userAgent": "@clerk/clerk-sdk-node@3.7.0", }, }, - "organizations": OrganizationApi { - "_restClient": RestClient { + "organizations": OrganizationAPI { + "APIClient": Clerk { + "_allowlistIdentifierAPI": AllowlistIdentifierAPI { + "APIClient": [Circular], + }, + "_clientAPI": ClientAPI { + "APIClient": [Circular], + }, + "_emailAPI": EmailAPI { + "APIClient": [Circular], + }, + "_invitationAPI": InvitationAPI { + "APIClient": [Circular], + }, + "_jwksClient": JwksClient { + "getSigningKey": [Function], + "options": Object { + "cache": true, + "cacheMaxAge": 3600000, + "jwksUri": "https://api.clerk.dev/v1/jwks", + "rateLimit": false, + "requestHeaders": Object { + "Authorization": "Bearer TEST_API_KEY", + }, + "timeout": 5000, + }, + }, + "_organizationAPI": [Circular], + "_sessionAPI": SessionAPI { + "APIClient": [Circular], + "getSession": [Function], + "getSessionList": [Function], + "getToken": [Function], + "revokeSession": [Function], + "verifySession": [Function], + }, + "_smsMessageAPI": SMSMessageAPI { + "APIClient": [Circular], + }, + "_userAPI": UserAPI { + "APIClient": [Circular], + }, + "apiClient": Object { + "request": [Function], + }, "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", + "base": Base { + "buildAuthenticatedState": [Function], + "decodeBase64Function": [Function], + "decodeJwt": [Function], + "getAuthState": [Function], + "importKeyFunction": [Function], + "loadCryptoKey": [Function], + "loadCryptoKeyFunction": [Function], + "verifyJwt": [Function], + "verifyJwtSignature": [Function], + "verifySessionToken": [Function], + "verifySignatureFunction": [Function], + }, + "httpOptions": Object {}, + "jwtKey": undefined, + "options": Object { + "apiClient": Object { + "request": [Function], + }, + "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", + "apiVersion": "v1", + "libName": "@clerk/clerk-sdk-node", + "libVersion": "3.7.0", + }, + "userAgent": "@clerk/clerk-sdk-node@3.7.0", }, }, "requireAuth": [Function], - "sessions": SessionApi { - "_restClient": RestClient { + "sessions": SessionAPI { + "APIClient": Clerk { + "_allowlistIdentifierAPI": AllowlistIdentifierAPI { + "APIClient": [Circular], + }, + "_clientAPI": ClientAPI { + "APIClient": [Circular], + }, + "_emailAPI": EmailAPI { + "APIClient": [Circular], + }, + "_invitationAPI": InvitationAPI { + "APIClient": [Circular], + }, + "_jwksClient": JwksClient { + "getSigningKey": [Function], + "options": Object { + "cache": true, + "cacheMaxAge": 3600000, + "jwksUri": "https://api.clerk.dev/v1/jwks", + "rateLimit": false, + "requestHeaders": Object { + "Authorization": "Bearer TEST_API_KEY", + }, + "timeout": 5000, + }, + }, + "_organizationAPI": OrganizationAPI { + "APIClient": [Circular], + }, + "_sessionAPI": [Circular], + "_smsMessageAPI": SMSMessageAPI { + "APIClient": [Circular], + }, + "_userAPI": UserAPI { + "APIClient": [Circular], + }, + "apiClient": Object { + "request": [Function], + }, "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", + "base": Base { + "buildAuthenticatedState": [Function], + "decodeBase64Function": [Function], + "decodeJwt": [Function], + "getAuthState": [Function], + "importKeyFunction": [Function], + "loadCryptoKey": [Function], + "loadCryptoKeyFunction": [Function], + "verifyJwt": [Function], + "verifyJwtSignature": [Function], + "verifySessionToken": [Function], + "verifySignatureFunction": [Function], + }, + "httpOptions": Object {}, + "jwtKey": undefined, + "options": Object { + "apiClient": Object { + "request": [Function], + }, + "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", + "apiVersion": "v1", + "libName": "@clerk/clerk-sdk-node", + "libVersion": "3.7.0", + }, + "userAgent": "@clerk/clerk-sdk-node@3.7.0", }, "getSession": [Function], "getSessionList": [Function], @@ -200,22 +556,156 @@ Object { "setClerkApiVersion": [Function], "setClerkHttpOptions": [Function], "setClerkServerApiUrl": [Function], - "smsMessages": SMSMessageApi { - "_restClient": RestClient { + "smsMessages": SMSMessageAPI { + "APIClient": Clerk { + "_allowlistIdentifierAPI": AllowlistIdentifierAPI { + "APIClient": [Circular], + }, + "_clientAPI": ClientAPI { + "APIClient": [Circular], + }, + "_emailAPI": EmailAPI { + "APIClient": [Circular], + }, + "_invitationAPI": InvitationAPI { + "APIClient": [Circular], + }, + "_jwksClient": JwksClient { + "getSigningKey": [Function], + "options": Object { + "cache": true, + "cacheMaxAge": 3600000, + "jwksUri": "https://api.clerk.dev/v1/jwks", + "rateLimit": false, + "requestHeaders": Object { + "Authorization": "Bearer TEST_API_KEY", + }, + "timeout": 5000, + }, + }, + "_organizationAPI": OrganizationAPI { + "APIClient": [Circular], + }, + "_sessionAPI": SessionAPI { + "APIClient": [Circular], + "getSession": [Function], + "getSessionList": [Function], + "getToken": [Function], + "revokeSession": [Function], + "verifySession": [Function], + }, + "_smsMessageAPI": [Circular], + "_userAPI": UserAPI { + "APIClient": [Circular], + }, + "apiClient": Object { + "request": [Function], + }, "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", + "base": Base { + "buildAuthenticatedState": [Function], + "decodeBase64Function": [Function], + "decodeJwt": [Function], + "getAuthState": [Function], + "importKeyFunction": [Function], + "loadCryptoKey": [Function], + "loadCryptoKeyFunction": [Function], + "verifyJwt": [Function], + "verifyJwtSignature": [Function], + "verifySessionToken": [Function], + "verifySignatureFunction": [Function], + }, + "httpOptions": Object {}, + "jwtKey": undefined, + "options": Object { + "apiClient": Object { + "request": [Function], + }, + "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", + "apiVersion": "v1", + "libName": "@clerk/clerk-sdk-node", + "libVersion": "3.7.0", + }, + "userAgent": "@clerk/clerk-sdk-node@3.7.0", }, }, - "users": UserApi { - "_restClient": RestClient { + "users": UserAPI { + "APIClient": Clerk { + "_allowlistIdentifierAPI": AllowlistIdentifierAPI { + "APIClient": [Circular], + }, + "_clientAPI": ClientAPI { + "APIClient": [Circular], + }, + "_emailAPI": EmailAPI { + "APIClient": [Circular], + }, + "_invitationAPI": InvitationAPI { + "APIClient": [Circular], + }, + "_jwksClient": JwksClient { + "getSigningKey": [Function], + "options": Object { + "cache": true, + "cacheMaxAge": 3600000, + "jwksUri": "https://api.clerk.dev/v1/jwks", + "rateLimit": false, + "requestHeaders": Object { + "Authorization": "Bearer TEST_API_KEY", + }, + "timeout": 5000, + }, + }, + "_organizationAPI": OrganizationAPI { + "APIClient": [Circular], + }, + "_sessionAPI": SessionAPI { + "APIClient": [Circular], + "getSession": [Function], + "getSessionList": [Function], + "getToken": [Function], + "revokeSession": [Function], + "verifySession": [Function], + }, + "_smsMessageAPI": SMSMessageAPI { + "APIClient": [Circular], + }, + "_userAPI": [Circular], + "apiClient": Object { + "request": [Function], + }, "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", "apiVersion": "v1", - "fetcher": [Function], - "serverApiUrl": "https://api.clerk.dev", - "userAgent": "@clerk/clerk-sdk-node/3.7.0 (https://github.com/clerkinc/clerk-sdk-node)", + "base": Base { + "buildAuthenticatedState": [Function], + "decodeBase64Function": [Function], + "decodeJwt": [Function], + "getAuthState": [Function], + "importKeyFunction": [Function], + "loadCryptoKey": [Function], + "loadCryptoKeyFunction": [Function], + "verifyJwt": [Function], + "verifyJwtSignature": [Function], + "verifySessionToken": [Function], + "verifySignatureFunction": [Function], + }, + "httpOptions": Object {}, + "jwtKey": undefined, + "options": Object { + "apiClient": Object { + "request": [Function], + }, + "apiKey": "TEST_API_KEY", + "apiUrl": "https://api.clerk.dev", + "apiVersion": "v1", + "libName": "@clerk/clerk-sdk-node", + "libVersion": "3.7.0", + }, + "userAgent": "@clerk/clerk-sdk-node@3.7.0", }, }, "withAuth": [Function], diff --git a/packages/sdk-node/src/__tests__/instance.test.ts b/packages/sdk-node/src/__tests__/instance.test.ts index 422a905dad7..5b6b6934347 100644 --- a/packages/sdk-node/src/__tests__/instance.test.ts +++ b/packages/sdk-node/src/__tests__/instance.test.ts @@ -37,13 +37,13 @@ describe('Custom Clerk instance initialization', () => { const instance = new Clerk({ apiKey: customAPIKey, jwtKey: customJWTKey, - serverApiUrl: customAPIUrl, + apiUrl: customAPIUrl, apiVersion: customAPIVersion, }); - expect(instance._restClient.apiKey).toBe(customAPIKey); + expect(instance.apiKey).toBe(customAPIKey); expect(instance.jwtKey).toBe(customJWTKey); - expect(instance._restClient.serverApiUrl).toBe(customAPIUrl); - expect(instance._restClient.apiVersion).toBe(customAPIVersion); + expect(instance.apiUrl).toBe(customAPIUrl); + expect(instance.apiVersion).toBe(customAPIVersion); }).not.toThrow(Error); }); }); diff --git a/packages/sdk-node/src/__tests__/middleware.test.ts b/packages/sdk-node/src/__tests__/middleware.test.ts index d253503109e..140db1992fd 100644 --- a/packages/sdk-node/src/__tests__/middleware.test.ts +++ b/packages/sdk-node/src/__tests__/middleware.test.ts @@ -1,10 +1,10 @@ +import { AuthStatus } from '@clerk/backend-core'; import type { NextFunction, Request, Response } from 'express'; import jwt from 'jsonwebtoken'; -import Clerk from '../Clerk'; -import { AuthStatus } from '@clerk/backend-core'; +import Clerk, { WithAuthProp } from '../Clerk'; -const mockGetToken = () => {}; +const mockGetToken = jest.fn(); jest.mock('@clerk/backend-core', () => { return { @@ -50,29 +50,27 @@ afterEach(() => { test('expressWithAuth with no session token', async () => { // @ts-ignore - const req = {} as Request; + const req = {} as WithAuthProp; const res = {} as Response; const clerk = Clerk.getInstance(); - await clerk.expressWithAuth()(req, res, mockNext as NextFunction); + const clerkMiddleware = clerk.expressWithAuth(); + await clerkMiddleware(req, res, mockNext as NextFunction); - // @ts-ignore expect(req.auth).toEqual(mockAuthSignedOutProp); - - expect(mockNext).toHaveBeenCalledWith(); // 0 args + expect(mockNext).toHaveBeenCalledWith(); }); test('expressRequireAuth with no session token', async () => { - // @ts-ignore - const req = {} as Request; + const req = {} as WithAuthProp; const res = {} as Response; const clerk = Clerk.getInstance(); - await clerk.expressRequireAuth()(req, res, mockNext as NextFunction); + const clerkRequireAuthMiddleware = clerk.expressRequireAuth(); + await clerkRequireAuthMiddleware(req, res, mockNext as NextFunction); - // @ts-ignore expect(req.auth).toEqual(mockAuthSignedOutProp); expect(mockNext).toHaveBeenCalled(); @@ -80,8 +78,9 @@ test('expressRequireAuth with no session token', async () => { }); test('expressWithAuth with Authorization header', async () => { - // @ts-ignore - const req = { headers: { authorization: mockToken } } as Request; + const req = { + headers: { authorization: mockToken }, + } as WithAuthProp; const res = {} as Response; const clerk = Clerk.getInstance(); @@ -89,16 +88,17 @@ test('expressWithAuth with Authorization header', async () => { .fn() .mockReturnValueOnce(mockGetAuthStateResult); - await clerk.expressWithAuth()(req, res, mockNext as NextFunction); + const clerkWithAuthMiddleware = clerk.expressWithAuth(); + await clerkWithAuthMiddleware(req, res, mockNext as NextFunction); - // @ts-ignore expect(req.auth).toEqual(mockAuthProp); expect(mockNext).toHaveBeenCalledWith(); }); test('expressWithAuth with Authorization header in Bearer format', async () => { - // @ts-ignore - const req = { headers: { authorization: `Bearer ${mockToken}` } } as Request; + const req = { + headers: { authorization: `Bearer ${mockToken}` }, + } as WithAuthProp; const res = {} as Response; const clerk = Clerk.getInstance(); @@ -106,36 +106,38 @@ test('expressWithAuth with Authorization header in Bearer format', async () => { .fn() .mockReturnValueOnce(mockGetAuthStateResult); - await clerk.expressWithAuth()(req, res, mockNext as NextFunction); + const clerkWithAuthMiddleware = clerk.expressWithAuth(); + await clerkWithAuthMiddleware(req, res, mockNext as NextFunction); - // @ts-ignore expect(req.auth).toEqual(mockAuthProp); - - expect(mockNext).toHaveBeenCalledWith(); // 0 args + expect(mockNext).toHaveBeenCalledWith(); }); test('expressWithAuth non-browser request (curl)', async () => { // @ts-ignore - const req = { headers: { 'User-Agent': 'curl/7.64.1' } } as Request; + const req = { + headers: { 'User-Agent': 'curl/7.64.1' }, + } as WithAuthProp; const res = {} as Response; const clerk = Clerk.getInstance(); - await clerk.expressWithAuth()(req, res, mockNext as NextFunction); - // @ts-ignore + const clerkWithAuthMiddleware = clerk.expressWithAuth(); + await clerkWithAuthMiddleware(req, res, mockNext as NextFunction); + expect(req.auth).toEqual(mockAuthSignedOutProp); - expect(mockNext).toHaveBeenCalledWith(); // 0 args + expect(mockNext).toHaveBeenCalledWith(); }); test('expressWithAuth with empty Authorization header', async () => { - // @ts-ignore - const req = { headers: { authorization: '' } } as Request; + const req = { headers: { authorization: '' } } as WithAuthProp; const res = {} as Response; const clerk = Clerk.getInstance(); - await clerk.expressWithAuth()(req, res, mockNext as NextFunction); - //@ts-ignore + const clerkWithAuthMiddleware = clerk.expressWithAuth(); + await clerkWithAuthMiddleware(req, res, mockNext as NextFunction); + expect(req.auth).toEqual(mockAuthSignedOutProp); - expect(mockNext).toHaveBeenCalledWith(); // 0 args + expect(mockNext).toHaveBeenCalledWith(); }); diff --git a/packages/sdk-node/src/index.ts b/packages/sdk-node/src/index.ts index 0a7c1ff715b..91eace4dec5 100644 --- a/packages/sdk-node/src/index.ts +++ b/packages/sdk-node/src/index.ts @@ -1,4 +1,4 @@ -import { OptionsOfUnknownResponseBody } from 'got'; +import type { RequestInit } from 'node-fetch'; import Clerk from './instance'; @@ -29,7 +29,6 @@ export { // Export resources export { - Nullable, AllowlistIdentifier, Client, Email, @@ -62,16 +61,6 @@ export { ClerkExpressWithAuth, ClerkExpressRequireAuth, withAuth, requireAuth }; // Export wrapper types for Next.js requests export { WithAuthProp, RequireAuthProp } from './Clerk'; -// Export Errors -export { - HttpError, - ClerkServerError, - ClerkServerErrorJSON, -} from './utils/Errors'; - -// Export Logger -export { default as Logger } from './utils/Logger'; - // Export setters for the default singleton instance // Useful if you only have access to a sub-api instance @@ -80,13 +69,13 @@ export function setClerkApiKey(value: string) { } export function setClerkServerApiUrl(value: string) { - Clerk.getInstance().serverApiUrl = value; + Clerk.getInstance().apiUrl = value; } export function setClerkApiVersion(value: string) { Clerk.getInstance().apiVersion = value; } -export function setClerkHttpOptions(value: OptionsOfUnknownResponseBody) { +export function setClerkHttpOptions(value: RequestInit) { Clerk.getInstance().httpOptions = value; } diff --git a/packages/sdk-node/src/info.ts b/packages/sdk-node/src/info.ts index cbb5c53bdb1..c103eb92c7f 100644 --- a/packages/sdk-node/src/info.ts +++ b/packages/sdk-node/src/info.ts @@ -1,3 +1,3 @@ /** DO NOT EDIT: This file is automatically generated by ../scripts/info.js */ -export const LIB_VERSION = '3.8.6'; +export const LIB_VERSION = '3.9.1'; export const LIB_NAME = '@clerk/clerk-sdk-node'; diff --git a/packages/sdk-node/src/instance.ts b/packages/sdk-node/src/instance.ts index 6245477bf1c..c6049e696f5 100644 --- a/packages/sdk-node/src/instance.ts +++ b/packages/sdk-node/src/instance.ts @@ -4,13 +4,6 @@ export default Clerk; export { WithAuthProp, RequireAuthProp } from './Clerk'; -export { - HttpError, - ClerkServerError, - ClerkServerErrorJSON, -} from './utils/Errors'; -export { default as Logger } from './utils/Logger'; - export { AllowlistIdentifier, Client, @@ -19,7 +12,6 @@ export { ExternalAccount, IdentificationLink, Invitation, - Nullable, Organization, OrganizationInvitation, OrganizationMembership, diff --git a/packages/sdk-node/src/utils/Crypto.ts b/packages/sdk-node/src/utils/Crypto.ts new file mode 100644 index 00000000000..c97406bf6cc --- /dev/null +++ b/packages/sdk-node/src/utils/Crypto.ts @@ -0,0 +1,43 @@ +import { Crypto, CryptoKey } from '@peculiar/webcrypto'; + +import { decodeBase64, str2ab } from './Encoding'; + +export const crypto = new Crypto(); + +// toSPKIDer converts a PEM encoded Public Key to DER encoded +export function toSPKIDer(pem: string): ArrayBuffer { + const pemHeader = '-----BEGIN PUBLIC KEY-----'; + const pemFooter = '-----END PUBLIC KEY-----'; + const pemContents = pem.substring( + pemHeader.length, + pem.length - pemFooter.length + ); + const binaryDerString = decodeBase64(pemContents); + return str2ab(binaryDerString); +} + +export async function importPKIKey(publicKey: string) { + return crypto.subtle.importKey( + 'spki', + toSPKIDer(publicKey), + { + name: 'RSASSA-PKCS1-v1_5', + hash: 'SHA-256', + }, + true, + ['verify'] + ); +} + +export async function importJSONWebKey(jwk: JsonWebKey, algorithm: Algorithm) { + return await crypto.subtle.importKey('jwk', jwk, algorithm, true, ['verify']); +} + +export async function verifySignature( + algorithm: Algorithm, + key: CryptoKey, + signature: Uint8Array, + data: Uint8Array +) { + return crypto.subtle.verify(algorithm, key, signature, data); +} diff --git a/packages/sdk-node/src/utils/Encoding.ts b/packages/sdk-node/src/utils/Encoding.ts new file mode 100644 index 00000000000..edb39b9c730 --- /dev/null +++ b/packages/sdk-node/src/utils/Encoding.ts @@ -0,0 +1,13 @@ +export function decodeBase64(base64: string) { + return Buffer.from(base64, 'base64').toString('binary'); +} + +// https://stackoverflow.com/a/11058858 +export function str2ab(input: string): ArrayBuffer { + const buf = new ArrayBuffer(input.length); + const bufView = new Uint8Array(buf); + for (let i = 0, strLen = input.length; i < strLen; i++) { + bufView[i] = input.charCodeAt(i); + } + return buf; +} diff --git a/packages/sdk-node/src/utils/Errors.ts b/packages/sdk-node/src/utils/Errors.ts deleted file mode 100644 index 21afe6d296d..00000000000 --- a/packages/sdk-node/src/utils/Errors.ts +++ /dev/null @@ -1,42 +0,0 @@ -export class HttpError extends Error { - public statusCode: number; - public data: unknown; - - constructor(statusCode: number, message: string, data: unknown) { - super(message); - this.statusCode = statusCode; - this.data = data; - } -} - -export interface ClerkServerErrorProps { - message: string; - longMessage: string; - code: string; -} - -export interface ClerkServerErrorJSON { - message: string; - long_message: string; - code: string; -} - -export class ClerkServerError { - public message: string; - public longMessage: string; - public code: string; - - constructor(data: ClerkServerErrorProps) { - this.message = data.message; - this.longMessage = data.longMessage; - this.code = data.code; - } - - static fromJSON(data: ClerkServerErrorJSON) { - return new ClerkServerError({ - message: data.message, - longMessage: data.long_message, - code: data.code, - }); - } -} diff --git a/packages/sdk-node/src/utils/Logger.ts b/packages/sdk-node/src/utils/Logger.ts deleted file mode 100644 index f8cb1d52cd3..00000000000 --- a/packages/sdk-node/src/utils/Logger.ts +++ /dev/null @@ -1,46 +0,0 @@ -// TODO use EventEmitter for an async Logger instead -// TODO support more than just console output - -enum LogLevel { - Info = 'INFO', - Debug = 'DEBUG', - Warn = 'WARN', - Error = 'ERROR', -} - -type LogMessage = { - level: LogLevel; - timestamp: string; - message: string; -}; - -export default class Logger { - public static info(msg: string): void { - Logger.log(LogLevel.Info, msg); - } - - public static debug(msg: string): void { - Logger.log(LogLevel.Debug, msg); - } - - public static warn(msg: string): void { - Logger.log(LogLevel.Warn, msg); - } - - public static error(msg: string): void { - Logger.log(LogLevel.Error, msg); - } - - private static log(logLevel: LogLevel, msg: string): void { - if (process.env.CLERK_LOGGING == 'true') { - const logMessage: LogMessage = { - timestamp: new Date().toISOString(), - level: logLevel, - message: msg, - }; - - // @ts-ignore - console[logLevel.toLowerCase()](logMessage); - } - } -} diff --git a/packages/sdk-node/src/utils/crypto.ts b/packages/sdk-node/src/utils/crypto.ts deleted file mode 100644 index 5c3e11083d9..00000000000 --- a/packages/sdk-node/src/utils/crypto.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const decodeBase64 = (base64: string) => - Buffer.from(base64, 'base64').toString('binary'); - -// toSPKIDer converts a PEM encoded Public Key to DER encoded -export function toSPKIDer(pem: string): ArrayBuffer { - const pemHeader = '-----BEGIN PUBLIC KEY-----'; - const pemFooter = '-----END PUBLIC KEY-----'; - const pemContents = pem.substring( - pemHeader.length, - pem.length - pemFooter.length - ); - const binaryDerString = decodeBase64(pemContents); - return str2ab(binaryDerString); -} - -// https://stackoverflow.com/a/11058858 -function str2ab(input: string): ArrayBuffer { - const buf = new ArrayBuffer(input.length); - const bufView = new Uint8Array(buf); - for (let i = 0, strLen = input.length; i < strLen; i++) { - bufView[i] = input.charCodeAt(i); - } - return buf; -} diff --git a/packages/sdk-node/src/utils/index.ts b/packages/sdk-node/src/utils/index.ts new file mode 100644 index 00000000000..d8ec93a02db --- /dev/null +++ b/packages/sdk-node/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './Crypto'; +export * from './Encoding'; diff --git a/packages/shared/CHANGELOG.md b/packages/shared/CHANGELOG.md index 56ae5727192..749d7287ee1 100644 --- a/packages/shared/CHANGELOG.md +++ b/packages/shared/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [0.3.7](https://github.com/clerkinc/clerk_docker/compare/@clerk/shared@0.3.6...@clerk/shared@0.3.7) (2022-08-04) + +**Note:** Version bump only for package @clerk/shared + ### [0.3.6](https://github.com/clerkinc/clerk_docker/compare/@clerk/shared@0.3.5...@clerk/shared@0.3.6) (2022-07-13) **Note:** Version bump only for package @clerk/shared diff --git a/packages/shared/package.json b/packages/shared/package.json index 985e4877b0b..a3f85c272fc 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/shared", - "version": "0.3.6", + "version": "0.3.7", "private": true, "main": "index.js", "module": "index.js", @@ -26,7 +26,7 @@ "@babel/core": "^7.13.14", "@babel/preset-env": "^7.13.12", "@babel/preset-react": "^7.13.13", - "@clerk/types": "^2.20.0", + "@clerk/types": "^2.21.0", "@popperjs/core": "^2.5.4", "@sentry/browser": "^6.3.0", "@svgr/webpack": "^6.2.1", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index cdd3579f3e1..3d8efd42da0 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.21.0](https://github.com/clerkinc/javascript/compare/@clerk/types@2.20.0...@clerk/types@2.21.0) (2022-08-04) + +### Features + +- **clerk-js:** Get support email from FAPI /v1/environment if exists ([c9bb8d7](https://github.com/clerkinc/javascript/commit/c9bb8d7aaf3958207d4799bdd30e3b15b2890a5d)) + ## [2.20.0](https://github.com/clerkinc/javascript/compare/@clerk/types@2.19.1...@clerk/types@2.20.0) (2022-07-13) ### Features diff --git a/packages/types/package.json b/packages/types/package.json index 5939842dc80..41f0e168d4c 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/types", - "version": "2.20.0", + "version": "2.21.0", "license": "MIT", "description": "Typings for Clerk libraries.", "keywords": [ diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 6d61d1b0707..e5c968df392 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -292,6 +292,7 @@ export type ClerkThemeOptions = DeepSnakeToCamel>; export interface ClerkOptions { navigate?: (to: string) => Promise | unknown; polling?: boolean; + touchSession?: boolean; selectInitialSession?: (client: ClientResource) => ActiveSessionResource | null; theme?: ClerkThemeOptions; /** Optional support email for display in authentication screens */ diff --git a/packages/types/src/displayConfig.ts b/packages/types/src/displayConfig.ts index 6f5e23279f7..10888d6f0af 100644 --- a/packages/types/src/displayConfig.ts +++ b/packages/types/src/displayConfig.ts @@ -6,44 +6,46 @@ export type PreferredSignInStrategy = 'password' | 'otp'; export interface DisplayConfigJSON { object: 'display_config'; id: string; - instance_environment_type: string; + after_sign_in_url: string; + after_sign_out_all_url: string; + after_sign_out_one_url: string; + after_sign_out_url: string; + after_sign_up_url: string; + after_switch_session_url: string; application_name: string; - theme: DisplayThemeJSON; - preferred_sign_in_strategy: PreferredSignInStrategy; - logo_image: ImageJSON; - favicon_image: ImageJSON; backend_host: string; + branded: boolean; + favicon_image: ImageJSON; home_url: string; + instance_environment_type: string; + logo_image: ImageJSON; + preferred_sign_in_strategy: PreferredSignInStrategy; sign_in_url: string; sign_up_url: string; + support_email: string; + theme: DisplayThemeJSON; user_profile_url: string; - after_sign_in_url: string; - after_sign_up_url: string; - after_sign_out_url: string; - after_sign_out_one_url: string; - after_sign_out_all_url: string; - after_switch_session_url: string; - branded: boolean; } export interface DisplayConfigResource extends ClerkResource { id: string; - instanceEnvironmentType: string; + afterSignInUrl: string; + afterSignOutAllUrl: string; + afterSignOutOneUrl: string; + afterSignOutUrl: string; + afterSignUpUrl: string; + afterSwitchSessionUrl: string; applicationName: string; - theme: DisplayThemeJSON; - preferredSignInStrategy: PreferredSignInStrategy; - logoImage: ImageJSON; - faviconImage: ImageJSON; backendHost: string; + branded: boolean; + faviconImage: ImageJSON; homeUrl: string; + instanceEnvironmentType: string; + logoImage: ImageJSON; + preferredSignInStrategy: PreferredSignInStrategy; signInUrl: string; signUpUrl: string; + supportEmail: string; + theme: DisplayThemeJSON; userProfileUrl: string; - afterSignInUrl: string; - afterSignUpUrl: string; - afterSignOutUrl: string; - afterSignOutOneUrl: string; - afterSignOutAllUrl: string; - afterSwitchSessionUrl: string; - branded: boolean; }