diff --git a/package-lock.json b/package-lock.json index ef720728a1..10d30e6f47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -794,7 +793,6 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -803,7 +801,6 @@ "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", @@ -870,7 +867,6 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "dev": true, "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-validator-option": "^7.23.5", @@ -886,7 +882,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "dependencies": { "yallist": "^3.0.2" } @@ -894,8 +889,7 @@ "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.23.10", @@ -1011,7 +1005,6 @@ "version": "7.23.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -1084,7 +1077,6 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -1135,7 +1127,6 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -1158,7 +1149,6 @@ "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", - "dev": true, "dependencies": { "@babel/template": "^7.23.9", "@babel/traverse": "^7.23.9", @@ -4529,6 +4519,23 @@ "node": ">=12" } }, + "node_modules/@n8n/vm2": { + "version": "3.9.23", + "resolved": "https://registry.npmjs.org/@n8n/vm2/-/vm2-3.9.23.tgz", + "integrity": "sha512-yu+It+L89uljQsCJ2e9cQaXzoXJe9bU69QQIoWUOcUw0u5Zon37DuB7bdNNsjKS1ZdFD+fBWCQpq/FkqHsSjXQ==", + "peer": true, + "dependencies": { + "acorn": "^8.7.0", + "acorn-walk": "^8.2.0" + }, + "bin": { + "vm2": "bin/vm2" + }, + "engines": { + "node": ">=18.10", + "pnpm": ">=8.6.12" + } + }, "node_modules/@next/env": { "version": "12.3.3", "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.3.tgz", @@ -6309,6 +6316,17 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/acorn-import-assertions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", @@ -6318,6 +6336,14 @@ "acorn": "^8" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", @@ -7401,7 +7427,6 @@ "version": "4.22.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7522,6 +7547,11 @@ "node": ">=0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -8428,8 +8458,7 @@ "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { "version": "0.6.0", @@ -9438,6 +9467,14 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -9691,8 +9728,7 @@ "node_modules/electron-to-chromium": { "version": "1.4.667", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.667.tgz", - "integrity": "sha512-66L3pLlWhTNVUhnmSA5+qDM3fwnXsM6KAqE36e2w4KN0g6pkEtlT5bs41FQtQwVwKnfhNBXiWRLPs30HSxd7Kw==", - "dev": true + "integrity": "sha512-66L3pLlWhTNVUhnmSA5+qDM3fwnXsM6KAqE36e2w4KN0g6pkEtlT5bs41FQtQwVwKnfhNBXiWRLPs30HSxd7Kw==" }, "node_modules/electron-util": { "version": "0.17.2", @@ -10816,7 +10852,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -13661,6 +13696,41 @@ "node": ">=12.0.0" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsprim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", @@ -13701,6 +13771,25 @@ "node": "*" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kew": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", @@ -13955,12 +14044,47 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -14694,8 +14818,7 @@ "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/node-vault": { "version": "0.10.2", @@ -17947,7 +18070,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "devOptional": true, "bin": { "semver": "bin/semver.js" } @@ -19204,18 +19326,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/terser/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -19727,7 +19837,6 @@ "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -20024,25 +20133,6 @@ "node": ">=6.0" } }, - "node_modules/vm2/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/vm2/node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/vscode-languageserver-types": { "version": "3.17.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", @@ -20242,18 +20332,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -21073,6 +21151,7 @@ "express-xml-bodyparser": "^0.3.0", "http-proxy": "^1.18.1", "js-yaml": "^4.1.0", + "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", "multer": "^1.4.5-lts.1" } diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js index 8eb4fee90f..35cf3a47d8 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js @@ -71,6 +71,15 @@ const AuthMode = ({ item, collection }) => { > Digest Auth +
{ + dropdownTippyRef.current.hide(); + onModeChange('oauth2'); + }} + > + OAuth 2.0 +
{ diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/StyledWrapper.js new file mode 100644 index 0000000000..e4d6a7d6c3 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/StyledWrapper.js @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js new file mode 100644 index 0000000000..08ac8dab11 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js @@ -0,0 +1,65 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { updateAuth } from 'providers/ReduxStore/slices/collections'; +import { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; +import { inputsConfig } from './inputsConfig'; + +const OAuth2AuthorizationCode = ({ item, collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {}); + + const handleRun = async () => { + dispatch(sendRequest(item, collection.uid)); + }; + + const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleChange = (key, value) => { + dispatch( + updateAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + grantType: 'authorization_code', + ...oAuth, + [key]: value + } + }) + ); + }; + + return ( + + {inputsConfig.map((input) => { + const { key, label } = input; + return ( +
+ +
+ handleChange(key, val)} + onRun={() => {}} + collection={collection} + /> +
+
+ ); + })} + +
+ ); +}; + +export default OAuth2AuthorizationCode; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/inputsConfig.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/inputsConfig.js new file mode 100644 index 0000000000..a1f3f3d45a --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/inputsConfig.js @@ -0,0 +1,28 @@ +const inputsConfig = [ + { + key: 'callbackUrl', + label: 'Callback URL' + }, + { + key: 'authorizationUrl', + label: 'Auth URL' + }, + { + key: 'accessTokenUrl', + label: 'Access Token URL' + }, + { + key: 'clientId', + label: 'Client ID' + }, + { + key: 'clientSecret', + label: 'Client Secret' + }, + { + key: 'scope', + label: 'Scope' + } +]; + +export { inputsConfig }; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/StyledWrapper.js new file mode 100644 index 0000000000..e4d6a7d6c3 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/StyledWrapper.js @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js new file mode 100644 index 0000000000..b8c460cac7 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js @@ -0,0 +1,78 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { updateAuth } from 'providers/ReduxStore/slices/collections'; +import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; + +const OAuth2ClientCredentials = ({ item, collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {}); + + const handleRun = () => dispatch(sendRequest(item, collection.uid)); + const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleClientIdChange = (clientId) => { + dispatch( + updateAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + grantType: 'client_credentials', + clientId: clientId, + clientSecret: oAuth.clientSecret + } + }) + ); + }; + + const handleClientSecretChange = (clientSecret) => { + dispatch( + updateAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + grantType: 'client_credentials', + clientId: oAuth.clientId, + clientSecret: clientSecret + } + }) + ); + }; + + return ( + + +
+ handleClientIdChange(val)} + onRun={handleRun} + collection={collection} + /> +
+ + +
+ handleClientSecretChange(val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); +}; + +export default OAuth2ClientCredentials; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/GrantTypeSelector/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/GrantTypeSelector/StyledWrapper.js new file mode 100644 index 0000000000..4423a49c1d --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/GrantTypeSelector/StyledWrapper.js @@ -0,0 +1,54 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + font-size: 0.8125rem; + + .grant-type-mode-selector { + padding: 0.5rem 0px; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + + .dropdown { + width: 100%; + + div[data-tippy-root] { + width: 100%; + } + .tippy-box { + width: 100%; + max-width: none !important; + + .tippy-content: { + width: 100%; + max-width: none !important; + } + } + } + + .grant-type-label { + width: 100%; + color: ${(props) => props.theme.colors.text.yellow}; + justify-content: space-between; + padding: 0 0.5rem; + } + + .dropdown-item { + padding: 0.2rem 0.6rem !important; + } + + .label-item { + padding: 0.2rem 0.6rem !important; + } + } + + .caret { + color: rgb(140, 140, 140); + fill: rgb(140 140 140); + } + label { + font-size: 0.8125rem; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/GrantTypeSelector/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/GrantTypeSelector/index.js new file mode 100644 index 0000000000..06a9b5e318 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/GrantTypeSelector/index.js @@ -0,0 +1,75 @@ +import React, { useRef, forwardRef } from 'react'; +import get from 'lodash/get'; +import Dropdown from 'components/Dropdown'; +import { useDispatch } from 'react-redux'; +import StyledWrapper from './StyledWrapper'; +import { IconCaretDown } from '@tabler/icons'; +import { updateAuth } from 'providers/ReduxStore/slices/collections'; +import { humanizeGrantType } from 'utils/collections'; + +const GrantTypeSelector = ({ item, collection }) => { + const dispatch = useDispatch(); + const dropdownTippyRef = useRef(); + const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); + + const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {}); + + const Icon = forwardRef((props, ref) => { + return ( +
+ {humanizeGrantType(oAuth?.grantType)} +
+ ); + }); + + const onGrantTypeChange = (grantType) => { + dispatch( + updateAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + grantType + } + }) + ); + }; + + return ( + + +
+ } placement="bottom-end"> +
{ + dropdownTippyRef.current.hide(); + onGrantTypeChange('password'); + }} + > + Resource Owner Password Credentials +
+
{ + dropdownTippyRef.current.hide(); + onGrantTypeChange('authorization_code'); + }} + > + Authorization Code +
+
{ + dropdownTippyRef.current.hide(); + onGrantTypeChange('client_credentials'); + }} + > + Client Credentials +
+
+
+
+ ); +}; +export default GrantTypeSelector; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/StyledWrapper.js new file mode 100644 index 0000000000..e4d6a7d6c3 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/StyledWrapper.js @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/index.js new file mode 100644 index 0000000000..104ebdd77f --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/index.js @@ -0,0 +1,78 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { updateAuth } from 'providers/ReduxStore/slices/collections'; +import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; + +const OAuth2Ropc = ({ item, collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {}); + + const handleRun = () => dispatch(sendRequest(item, collection.uid)); + const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleUsernameChange = (username) => { + dispatch( + updateAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + grantType: 'password', + username: username, + password: oAuth.password + } + }) + ); + }; + + const handlePasswordChange = (password) => { + dispatch( + updateAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + grantType: 'password', + username: oAuth.username, + password: password + } + }) + ); + }; + + return ( + + +
+ handleUsernameChange(val)} + onRun={handleRun} + collection={collection} + /> +
+ + +
+ handlePasswordChange(val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); +}; + +export default OAuth2Ropc; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/StyledWrapper.js new file mode 100644 index 0000000000..e4d6a7d6c3 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/StyledWrapper.js @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js new file mode 100644 index 0000000000..1d51962a57 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js @@ -0,0 +1,37 @@ +import React from 'react'; +import get from 'lodash/get'; +import StyledWrapper from './StyledWrapper'; +import GrantTypeSelector from './GrantTypeSelector/index'; +import OAuth2Ropc from './Ropc/index'; +import OAuth2AuthorizationCode from './AuthorizationCode/index'; +import OAuth2ClientCredentials from './ClientCredentials/index'; + +const grantTypeComponentMap = (grantType, item, collection) => { + switch (grantType) { + case 'password': + return ; + break; + case 'authorization_code': + return ; + break; + case 'client_credentials': + return ; + break; + default: + return
TBD
; + break; + } +}; + +const OAuth2 = ({ item, collection }) => { + const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {}); + + return ( + + + {grantTypeComponentMap(oAuth?.grantType, item, collection)} + + ); +}; + +export default OAuth2; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js index bd388737e9..04cc954f34 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js @@ -6,6 +6,7 @@ import BearerAuth from './BearerAuth'; import BasicAuth from './BasicAuth'; import DigestAuth from './DigestAuth'; import StyledWrapper from './StyledWrapper'; +import OAuth2 from './OAuth2/index'; const Auth = ({ item, collection }) => { const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode'); @@ -24,6 +25,9 @@ const Auth = ({ item, collection }) => { case 'digest': { return ; } + case 'oauth2': { + return ; + } } }; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index a5f9100c00..41b284149f 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -402,6 +402,10 @@ export const collectionsSlice = createSlice({ item.draft.request.auth.mode = 'digest'; item.draft.request.auth.digest = action.payload.content; break; + case 'oauth2': + item.draft.request.auth.mode = 'oauth2'; + item.draft.request.auth.oauth2 = action.payload.content; + break; } } } diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 216d74a4de..c18dbe7cef 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -505,6 +505,30 @@ export const humanizeRequestAuthMode = (mode) => { label = 'Digest Auth'; break; } + case 'oauth2': { + label = 'OAuth 2.0'; + break; + } + } + + return label; +}; + +export const humanizeGrantType = (mode) => { + let label = 'No Auth'; + switch (mode) { + case 'password': { + label = 'Resource Owner Password Credentials'; + break; + } + case 'authorization_code': { + label = 'Authorization Code'; + break; + } + case 'client_credentials': { + label = 'Client Credentials'; + break; + } } return label; diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index ef0e141dca..52afec0a8c 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -21,7 +21,8 @@ const contentSecurityPolicy = [ "script-src * 'unsafe-inline' 'unsafe-eval'", "connect-src * 'unsafe-inline'", "font-src 'self' https:", - "form-action 'none'", + // this has been commented out to make oauth2 work + // "form-action 'none'", "img-src 'self' blob: data: https:", "style-src 'self' 'unsafe-inline' https:" ]; diff --git a/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js b/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js new file mode 100644 index 0000000000..e4439f612a --- /dev/null +++ b/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js @@ -0,0 +1,61 @@ +const { BrowserWindow } = require('electron'); + +const authorizeUserInWindow = ({ authorizeUrl, callbackUrl }) => { + return new Promise(async (resolve, reject) => { + let finalUrl = null; + + const window = new BrowserWindow({ + webPreferences: { + nodeIntegration: false + }, + show: false + }); + window.on('ready-to-show', window.show.bind(window)); + + function onWindowRedirect(url) { + // check if the url contains an authorization code + if (url.match(/(code=).*/)) { + finalUrl = url; + if (url && finalUrl.includes(callbackUrl)) { + window.close(); + } else { + reject(new Error('Invalid Callback Url')); + } + } + } + + window.on('close', () => { + if (finalUrl) { + try { + const callbackUrlWithCode = new URL(finalUrl); + const authorizationCode = callbackUrlWithCode.searchParams.get('code'); + + return resolve(authorizationCode); + } catch (error) { + return reject(error); + } + } else { + return reject(new Error('Authorization window closed')); + } + }); + + // wait for the window to navigate to the callback url + const didNavigateListener = (_, url) => { + onWindowRedirect(url); + }; + window.webContents.on('did-navigate', didNavigateListener); + const willRedirectListener = (_, authorizeUrl) => { + onWindowRedirect(authorizeUrl); + }; + window.webContents.on('will-redirect', willRedirectListener); + + try { + await window.loadURL(authorizeUrl); + } catch (error) { + reject(error); + window.close(); + } + }); +}; + +module.exports = { authorizeUserInWindow }; diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index b068193c72..3446d52566 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -9,7 +9,7 @@ const Mustache = require('mustache'); const contentDispositionParser = require('content-disposition'); const mime = require('mime-types'); const { ipcMain } = require('electron'); -const { isUndefined, isNull, each, get, compact } = require('lodash'); +const { isUndefined, isNull, each, get, compact, cloneDeep } = require('lodash'); const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js'); const prepareRequest = require('./prepare-request'); const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request'); @@ -29,6 +29,7 @@ const { addDigestInterceptor } = require('./digestauth-helper'); const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util'); const { chooseFileToSave, writeBinaryFile } = require('../../utils/filesystem'); const { getCookieStringForUrl, addCookieToJar, getDomainsWithCookies } = require('../../utils/cookies'); +const { resolveOAuth2AuthorizationCodecessToken } = require('./oauth2-authorization-code-helper'); // override the default escape function to prevent escaping Mustache.escape = function (value) { @@ -189,6 +190,16 @@ const configureRequest = async ( const axiosInstance = makeAxiosInstance(); + if (request.oauth2) { + if (request?.oauth2?.grantType == 'authorization_code') { + let requestCopy = cloneDeep(request); + interpolateVars(requestCopy, envVars, collectionVariables, processEnvVars); + const { data, url } = await resolveOAuth2AuthorizationCodecessToken(requestCopy); + request.data = data; + request.url = url; + } + } + if (request.awsv4config) { request.awsv4config = await resolveAwsV4Credentials(request); addAwsV4Interceptor(axiosInstance, request); @@ -484,7 +495,6 @@ const registerNetworkIpc = (mainWindow) => { setCookieHeaders = Array.isArray(response.headers['set-cookie']) ? response.headers['set-cookie'] : [response.headers['set-cookie']]; - for (let setCookieHeader of setCookieHeaders) { if (typeof setCookieHeader === 'string' && setCookieHeader.length) { addCookieToJar(setCookieHeader, request.url); @@ -495,6 +505,7 @@ const registerNetworkIpc = (mainWindow) => { // send domain cookies to renderer const domainsWithCookies = await getDomainsWithCookies(); + mainWindow.webContents.send('main:cookies-update', safeParseJSON(safeStringifyJSON(domainsWithCookies))); await runPostResponse( diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 417f2df719..abf06bd862 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -98,17 +98,53 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces } // todo: we have things happening in two places w.r.t basic auth - // need to refactor this in the future + // need to refactor this in the future // the request.auth (basic auth) object gets set inside the prepare-request.js file if (request.auth) { const username = _interpolate(request.auth.username) || ''; const password = _interpolate(request.auth.password) || ''; - // use auth header based approach and delete the request.auth object request.headers['authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`; delete request.auth; } + if (request?.oauth2?.grantType) { + switch (request.oauth2.grantType) { + case 'password': + let username = _interpolate(request.oauth2.username) || ''; + let password = _interpolate(request.oauth2.password) || ''; + request.oauth2.username = username; + request.oauth2.password = password; + request.data = { + grant_type: 'password', + username, + password + }; + break; + case 'authorization_code': + request.oauth2.callbackUrl = _interpolate(request.oauth2.callbackUrl) || ''; + request.oauth2.authorizationUrl = _interpolate(request.oauth2.authorizationUrl) || ''; + request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || ''; + request.oauth2.clientId = _interpolate(request.oauth2.clientId) || ''; + request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || ''; + request.oauth2.scope = _interpolate(request.oauth2.scope) || ''; + break; + case 'client_credentials': + let clientId = _interpolate(request.oauth2.clientId) || ''; + let clientSecret = _interpolate(request.oauth2.clientSecret) || ''; + request.oauth2.clientId = clientId; + request.oauth2.clientSecret = clientSecret; + request.data = { + grant_type: 'client_credentials', + client_id: clientId, + client_secret: clientSecret + }; + break; + default: + break; + } + } + // interpolate vars for aws sigv4 auth if (request.awsv4config) { request.awsv4config.accessKeyId = _interpolate(request.awsv4config.accessKeyId) || ''; diff --git a/packages/bruno-electron/src/ipc/network/oauth2-authorization-code-helper.js b/packages/bruno-electron/src/ipc/network/oauth2-authorization-code-helper.js new file mode 100644 index 0000000000..303af81709 --- /dev/null +++ b/packages/bruno-electron/src/ipc/network/oauth2-authorization-code-helper.js @@ -0,0 +1,41 @@ +const { get, cloneDeep } = require('lodash'); +const { authorizeUserInWindow } = require('./authorize-user-in-window'); + +const resolveOAuth2AuthorizationCodecessToken = async (request) => { + let requestCopy = cloneDeep(request); + const authorization_code = await getOAuth2AuthorizationCode(requestCopy); + const oAuth = get(requestCopy, 'oauth2', {}); + const { clientId, clientSecret, callbackUrl, scope } = oAuth; + const data = { + grant_type: 'authorization_code', + code: authorization_code, + redirect_uri: callbackUrl, + client_id: clientId, + client_secret: clientSecret, + scope: scope + }; + const url = requestCopy?.oauth2?.accessTokenUrl; + return { + data, + url + }; +}; + +const getOAuth2AuthorizationCode = (request) => { + return new Promise(async (resolve, reject) => { + const { oauth2 } = request; + const { callbackUrl, clientId, authorizationUrl, scope } = oauth2; + const authorizationUrlWithQueryParams = `${authorizationUrl}?client_id=${clientId}&redirect_uri=${callbackUrl}&response_type=code&scope=${scope}`; + try { + const code = await authorizeUserInWindow({ authorizeUrl: authorizationUrlWithQueryParams, callbackUrl }); + resolve(code); + } catch (err) { + reject(err); + } + }); +}; + +module.exports = { + resolveOAuth2AuthorizationCodecessToken, + getOAuth2AuthorizationCode +}; diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index d793ad9386..f7317b59bc 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -62,6 +62,36 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { password: get(collectionAuth, 'digest.password') }; break; + case 'oauth2': + const grantType = get(collectionAuth, 'auth.oauth2.grantType'); + switch (grantType) { + case 'password': + axiosRequest.oauth2 = { + grantType: grantType, + username: get(collectionAuth, 'auth.oauth2.username'), + password: get(collectionAuth, 'auth.oauth2.password') + }; + break; + case 'authorization_code': + axiosRequest.oauth2 = { + grantType: grantType, + callbackUrl: get(collectionAuth, 'auth.oauth2.callbackUrl'), + authorizationUrl: get(collectionAuth, 'auth.oauth2.authorizationUrl'), + accessTokenUrl: get(collectionAuth, 'auth.oauth2.accessTokenUrl'), + clientId: get(collectionAuth, 'auth.oauth2.clientId'), + clientSecret: get(collectionAuth, 'auth.oauth2.clientSecret'), + scope: get(collectionAuth, 'auth.oauth2.scope') + }; + break; + case 'client_credentials': + axiosRequest.oauth2 = { + grantType: grantType, + clientId: get(collectionAuth, 'auth.oauth2.clientId'), + clientSecret: get(collectionAuth, 'auth.oauth2.clientSecret') + }; + break; + } + break; } } @@ -91,6 +121,37 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { username: get(request, 'auth.digest.username'), password: get(request, 'auth.digest.password') }; + break; + case 'oauth2': + const grantType = get(request, 'auth.oauth2.grantType'); + switch (grantType) { + case 'password': + axiosRequest.oauth2 = { + grantType: grantType, + username: get(request, 'auth.oauth2.username'), + password: get(request, 'auth.oauth2.password') + }; + break; + case 'authorization_code': + axiosRequest.oauth2 = { + grantType: grantType, + callbackUrl: get(request, 'auth.oauth2.callbackUrl'), + authorizationUrl: get(request, 'auth.oauth2.authorizationUrl'), + accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), + clientId: get(request, 'auth.oauth2.clientId'), + clientSecret: get(request, 'auth.oauth2.clientSecret'), + scope: get(request, 'auth.oauth2.scope') + }; + break; + case 'client_credentials': + axiosRequest.oauth2 = { + grantType: grantType, + clientId: get(request, 'auth.oauth2.clientId'), + clientSecret: get(request, 'auth.oauth2.clientSecret') + }; + break; + } + break; } } diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 3c2a6c8b08..71c3e9e6c8 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -23,7 +23,7 @@ const { outdentString } = require('../../v1/src/utils'); */ const grammar = ohm.grammar(`Bru { BruFile = (meta | http | query | headers | auths | bodies | varsandassert | script | tests | docs)* - auths = authawsv4 | authbasic | authbearer | authdigest + auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2 bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body bodyforms = bodyformurlencoded | bodymultipart @@ -80,6 +80,7 @@ const grammar = ohm.grammar(`Bru { authbasic = "auth:basic" dictionary authbearer = "auth:bearer" dictionary authdigest = "auth:digest" dictionary + authOAuth2 = "auth:oauth2" dictionary body = "body" st* "{" nl* textblock tagend bodyjson = "body:json" st* "{" nl* textblock tagend @@ -380,6 +381,46 @@ const sem = grammar.createSemantics().addAttribute('ast', { } }; }, + authOAuth2(_1, dictionary) { + const auth = mapPairListToKeyValPairs(dictionary.ast, false); + const grantTypeKey = _.find(auth, { name: 'grant_type' }); + const usernameKey = _.find(auth, { name: 'username' }); + const passwordKey = _.find(auth, { name: 'password' }); + const callbackUrlKey = _.find(auth, { name: 'callback_url' }); + const authorizationUrlKey = _.find(auth, { name: 'authorization_url' }); + const accessTokenUrlKey = _.find(auth, { name: 'access_token_url' }); + const clientIdKey = _.find(auth, { name: 'client_id' }); + const clientSecretKey = _.find(auth, { name: 'client_secret' }); + const scopeKey = _.find(auth, { name: 'scope' }); + return { + auth: { + oauth2: + grantTypeKey?.value && grantTypeKey?.value == 'password' + ? { + grantType: grantTypeKey ? grantTypeKey.value : '', + username: usernameKey ? usernameKey.value : '', + password: passwordKey ? passwordKey.value : '' + } + : grantTypeKey?.value && grantTypeKey?.value == 'authorization_code' + ? { + grantType: grantTypeKey ? grantTypeKey.value : '', + callbackUrl: callbackUrlKey ? callbackUrlKey.value : '', + authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '', + accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', + clientId: clientIdKey ? clientIdKey.value : '', + clientSecret: clientSecretKey ? clientSecretKey.value : '', + scope: scopeKey ? scopeKey.value : '' + } + : grantTypeKey?.value && grantTypeKey?.value == 'client_credentials' + ? { + grantType: grantTypeKey ? grantTypeKey.value : '', + clientId: clientIdKey ? clientIdKey.value : '', + clientSecret: clientSecretKey ? clientSecretKey.value : '' + } + : {} + } + }; + }, bodyformurlencoded(_1, dictionary) { return { body: { diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index 961a270e40..17b5514491 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -126,6 +126,42 @@ ${indentString(`password: ${auth.digest.password}`)} `; } + if (auth && auth.oauth2) { + switch (auth?.oauth2?.grantType) { + case 'password': + bru += `auth:oauth2 { +${indentString(`grant_type: password`)} +${indentString(`username: ${auth.oauth2.username}`)} +${indentString(`password: ${auth.oauth2.password}`)} +} + +`; + break; + case 'authorization_code': + bru += `auth:oauth2 { +${indentString(`grant_type: authorization_code`)} +${indentString(`callback_url: ${auth.oauth2.callbackUrl}`)} +${indentString(`authorization_url: ${auth.oauth2.authorizationUrl}`)} +${indentString(`access_token_url: ${auth.oauth2.accessTokenUrl}`)} +${indentString(`client_id: ${auth.oauth2.clientId}`)} +${indentString(`client_secret: ${auth.oauth2.clientSecret}`)} +${indentString(`scope: ${auth.oauth2.scope}`)} +} + +`; + break; + case 'client_credentials': + bru += `auth:oauth2 { +${indentString(`grant_type: client_credentials`)} +${indentString(`client_id: ${auth.oauth2.clientId}`)} +${indentString(`client_secret: ${auth.oauth2.clientSecret}`)} +} + +`; + break; + } + } + if (body && body.json && body.json.length) { bru += `body:json { ${indentString(body.json)} diff --git a/packages/bruno-lang/v2/tests/fixtures/request.bru b/packages/bruno-lang/v2/tests/fixtures/request.bru index 21b20477bb..20f55e07b4 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.bru +++ b/packages/bruno-lang/v2/tests/fixtures/request.bru @@ -45,6 +45,15 @@ auth:digest { password: secret } +auth:oauth2 { + grantType: authorization_code + client_id: client_id_1 + client_secret: client_secret_1 + auth_url: http://localhost:8080/api/auth/oauth2/ac/authorize + callback_url: http://localhost:8080/api/auth/oauth2/ac/callback + access_token_url: http://localhost:8080/api/auth/oauth2/ac/token +} + body:json { { "hello": "world" diff --git a/packages/bruno-lang/v2/tests/fixtures/request.json b/packages/bruno-lang/v2/tests/fixtures/request.json index 778da60b28..edfb6fbb8f 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.json +++ b/packages/bruno-lang/v2/tests/fixtures/request.json @@ -63,6 +63,14 @@ "digest": { "username": "john", "password": "secret" + }, + "oauth2": { + "grantType": "authorization_code", + "client_id": "client_id_1", + "client_secret": "client_secret_1", + "auth_url": "http://localhost:8080/api/auth/oauth2/ac/authorize", + "callback_url": "http://localhost:8080/api/auth/oauth2/ac/callback", + "access_token_url": "http://localhost:8080/api/auth/oauth2/ac/token" } }, "body": { diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index 5f7dc12372..3640a47bd1 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -119,12 +119,61 @@ const authDigestSchema = Yup.object({ .noUnknown(true) .strict(); +const oauth2Schema = Yup.object({ + grantType: Yup.string() + .oneOf(['client_credentials', 'password', 'authorization_code']) + .required('grantType is required'), + username: Yup.string().when('grantType', { + is: (val) => ['client_credentials', 'password'].includes(val), + then: Yup.string().nullable(), + otherwise: Yup.string().nullable().strip() + }), + password: Yup.string().when('grantType', { + is: (val) => ['client_credentials', 'password'].includes(val), + then: Yup.string().nullable(), + otherwise: Yup.string().nullable().strip() + }), + callbackUrl: Yup.string().when('grantType', { + is: (val) => ['authorization_code'].includes(val), + then: Yup.string().nullable(), + otherwise: Yup.string().nullable().strip() + }), + authorizationUrl: Yup.string().when('grantType', { + is: (val) => ['authorization_code'].includes(val), + then: Yup.string().nullable(), + otherwise: Yup.string().nullable().strip() + }), + accessTokenUrl: Yup.string().when('grantType', { + is: (val) => ['authorization_code'].includes(val), + then: Yup.string().nullable(), + otherwise: Yup.string().nullable().strip() + }), + clientId: Yup.string().when('grantType', { + is: (val) => ['authorization_code', 'client_credentials'].includes(val), + then: Yup.string().nullable(), + otherwise: Yup.string().nullable().strip() + }), + clientSecret: Yup.string().when('grantType', { + is: (val) => ['authorization_code', 'client_credentials'].includes(val), + then: Yup.string().nullable(), + otherwise: Yup.string().nullable().strip() + }), + scope: Yup.string().when('grantType', { + is: (val) => ['authorization_code'].includes(val), + then: Yup.string().nullable(), + otherwise: Yup.string().nullable().strip() + }) +}) + .noUnknown(true) + .strict(); + const authSchema = Yup.object({ - mode: Yup.string().oneOf(['none', 'awsv4', 'basic', 'bearer', 'digest']).required('mode is required'), + mode: Yup.string().oneOf(['none', 'awsv4', 'basic', 'bearer', 'digest', 'oauth2']).required('mode is required'), awsv4: authAwsV4Schema.nullable(), basic: authBasicSchema.nullable(), bearer: authBearerSchema.nullable(), - digest: authDigestSchema.nullable() + digest: authDigestSchema.nullable(), + oauth2: oauth2Schema.nullable() }) .noUnknown(true) .strict(); diff --git a/packages/bruno-tests/collection/auth/basic/via auth/Basic Auth 200.bru b/packages/bruno-tests/collection/auth/basic/via auth/Basic Auth 200.bru index de5612b1f5..32fdf59d1e 100644 --- a/packages/bruno-tests/collection/auth/basic/via auth/Basic Auth 200.bru +++ b/packages/bruno-tests/collection/auth/basic/via auth/Basic Auth 200.bru @@ -15,6 +15,10 @@ auth:basic { password: {{basic_auth_password}} } +body:json { + {"x":1} +} + assert { res.status: 200 res.body.message: Authentication successful diff --git a/packages/bruno-tests/collection/auth/basic/via script/Basic Auth 200.bru b/packages/bruno-tests/collection/auth/basic/via script/Basic Auth 200.bru index 04d12f2265..1afe97b6cc 100644 --- a/packages/bruno-tests/collection/auth/basic/via script/Basic Auth 200.bru +++ b/packages/bruno-tests/collection/auth/basic/via script/Basic Auth 200.bru @@ -10,6 +10,10 @@ post { auth: none } +body:json { + {"x":1} +} + assert { res.status: eq 200 res.body.message: Authentication successful diff --git a/packages/bruno-tests/collection/auth/oauth2/ac/github token with authorize.bru b/packages/bruno-tests/collection/auth/oauth2/ac/github token with authorize.bru new file mode 100644 index 0000000000..c18d2c6ed2 --- /dev/null +++ b/packages/bruno-tests/collection/auth/oauth2/ac/github token with authorize.bru @@ -0,0 +1,25 @@ +meta { + name: github token with authorize + type: http + seq: 1 +} + +post { + url: github.com + body: none + auth: oauth2 +} + +auth:oauth2 { + grant_type: authorization_code + callback_url: {{callback_url}} + authorization_url: {{github_authorize_url}} + access_token_url: {{github_access_token_url}} + client_id: {{github_client_id}} + client_secret: {{github_client_secret}} + scope: repo,gist +} + +script:post-response { + bru.setEnvVar('github_access_token',res.body.split('access_token=')[1]?.split('&scope')[0]); +} diff --git a/packages/bruno-tests/collection/auth/oauth2/ac/google token with authorize.bru b/packages/bruno-tests/collection/auth/oauth2/ac/google token with authorize.bru new file mode 100644 index 0000000000..93ea7975e6 --- /dev/null +++ b/packages/bruno-tests/collection/auth/oauth2/ac/google token with authorize.bru @@ -0,0 +1,25 @@ +meta { + name: google token with authorize + type: http + seq: 4 +} + +post { + url: + body: none + auth: oauth2 +} + +auth:oauth2 { + grant_type: authorization_code + callback_url: {{callback_url}} + authorization_url: {{google_auth_url}} + access_token_url: {{google_access_token_url}} + client_id: {{google_client_id}} + client_secret: {{google_client_secret}} + scope: {{google_scope}} +} + +script:post-response { + bru.setEnvVar('ac_access_token', res.body.access_token); +} diff --git a/packages/bruno-tests/collection/auth/oauth2/ac/resource.bru b/packages/bruno-tests/collection/auth/oauth2/ac/resource.bru new file mode 100644 index 0000000000..ead30ec6bf --- /dev/null +++ b/packages/bruno-tests/collection/auth/oauth2/ac/resource.bru @@ -0,0 +1,27 @@ +meta { + name: resource + type: http + seq: 3 +} + +post { + url: {{host}}/api/auth/oauth2/ac/resource?token={{ac_access_token}} + body: json + auth: none +} + +query { + token: {{ac_access_token}} +} + +auth:bearer { + token: +} + +body:json { + { + "code": "eb30dbf783b65bec4539ee1dcb068606", + "client_id": "{{client_id}}", + "client_secret": "{{client_secret}}" + } +} diff --git a/packages/bruno-tests/collection/auth/oauth2/ac/token with authorize.bru b/packages/bruno-tests/collection/auth/oauth2/ac/token with authorize.bru new file mode 100644 index 0000000000..e42fd7c776 --- /dev/null +++ b/packages/bruno-tests/collection/auth/oauth2/ac/token with authorize.bru @@ -0,0 +1,25 @@ +meta { + name: token with authorize + type: http + seq: 4 +} + +post { + url: + body: none + auth: oauth2 +} + +auth:oauth2 { + grant_type: authorization_code + callback_url: {{callback_url}} + authorization_url: {{auth_url}} + access_token_url: {{access_token_url}} + client_id: {{client_id}} + client_secret: {{client_secret}} + scope: +} + +script:post-response { + bru.setEnvVar('ac_access_token', res.body.access_token); +} diff --git a/packages/bruno-tests/collection/auth/oauth2/cc/resource.bru b/packages/bruno-tests/collection/auth/oauth2/cc/resource.bru new file mode 100644 index 0000000000..c4a1ce3992 --- /dev/null +++ b/packages/bruno-tests/collection/auth/oauth2/cc/resource.bru @@ -0,0 +1,15 @@ +meta { + name: resource + type: http + seq: 2 +} + +get { + url: {{host}}/api/auth/oauth2/cc/resource?token={{cc_access_token}} + body: none + auth: none +} + +query { + token: {{cc_access_token}} +} diff --git a/packages/bruno-tests/collection/auth/oauth2/cc/token.bru b/packages/bruno-tests/collection/auth/oauth2/cc/token.bru new file mode 100644 index 0000000000..13987b2ebe --- /dev/null +++ b/packages/bruno-tests/collection/auth/oauth2/cc/token.bru @@ -0,0 +1,21 @@ +meta { + name: token + type: http + seq: 1 +} + +post { + url: {{host}}/api/auth/oauth2/cc/token + body: none + auth: oauth2 +} + +auth:oauth2 { + grant_type: client_credentials + client_id: {{client_id}} + client_secret: {{client_secret}} +} + +script:post-response { + bru.setEnvVar('cc_access_token', res.body.access_token); +} diff --git a/packages/bruno-tests/collection/auth/oauth2/ropc/resource.bru b/packages/bruno-tests/collection/auth/oauth2/ropc/resource.bru new file mode 100644 index 0000000000..1395250ee8 --- /dev/null +++ b/packages/bruno-tests/collection/auth/oauth2/ropc/resource.bru @@ -0,0 +1,15 @@ +meta { + name: resource + type: http + seq: 2 +} + +post { + url: {{host}}/api/auth/oauth2/ropc/resource + body: none + auth: bearer +} + +auth:bearer { + token: {{ropc_access_token}} +} diff --git a/packages/bruno-tests/collection/auth/oauth2/ropc/token.bru b/packages/bruno-tests/collection/auth/oauth2/ropc/token.bru new file mode 100644 index 0000000000..495655ab36 --- /dev/null +++ b/packages/bruno-tests/collection/auth/oauth2/ropc/token.bru @@ -0,0 +1,21 @@ +meta { + name: token + type: http + seq: 1 +} + +post { + url: {{host}}/api/auth/oauth2/ropc/token + body: none + auth: oauth2 +} + +auth:oauth2 { + grant_type: password + username: {{ropc_username}} + password: {{ropc_password}} +} + +script:post-response { + bru.setEnvVar('ropc_access_token', res.body.access_token); +} diff --git a/packages/bruno-tests/collection/environments/Local.bru b/packages/bruno-tests/collection/environments/Local.bru index 26d3a65752..d4c08d9c8b 100644 --- a/packages/bruno-tests/collection/environments/Local.bru +++ b/packages/bruno-tests/collection/environments/Local.bru @@ -1,8 +1,22 @@ vars { - host: http://localhost:80 + host: http://localhost:8080 bearer_auth_token: your_secret_token basic_auth_password: della - env.var1: envVar1 - env-var2: envVar2 - bark: {{process.env.PROC_ENV_VAR}} + client_id: client_id_1 + client_secret: client_secret_1 + auth_url: http://localhost:8080/api/auth/oauth2/ac/authorize + callback_url: http://localhost:8080/api/auth/oauth2/ac/callback + access_token_url: http://localhost:8080/api/auth/oauth2/ac/token + ropc_username: foo + ropc_password: bar + github_client_secret: 4634189fc1f44fd2d6052178f960446dfd9aa0d1 + github_client_id: 8a75a075912b5e979786 + github_authorize_url: https://github.com/login/oauth/authorize + github_access_token_url: https://github.com/login/oauth/access_token + google_auth_url: https://accounts.google.com/o/oauth2/auth + google_access_token_url: https://accounts.google.com/o/oauth2/token + google_client_id: 156878810073-1insgtpe5t45amrs0mjfa69ld2n5kkbt.apps.googleusercontent.com + google_client_secret: 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 + google_scope: https://www.googleapis.com/auth/userinfo.email + github_authorization_code: 13285dba60810ab20981 } diff --git a/packages/bruno-tests/collection/ping.bru b/packages/bruno-tests/collection/ping.bru index eea7bae3af..b09936a9cc 100644 --- a/packages/bruno-tests/collection/ping.bru +++ b/packages/bruno-tests/collection/ping.bru @@ -29,6 +29,21 @@ assert { ~res.body: eq {{pong}} } +script:pre-request { + const project = 'foo' + const roles = ('abc, xyz'.split(",")).map(element => { + return element.trim(); + }); + + var project_roles = []; + + for (let i in roles) { + project_roles.push("\""+project+"_"+roles[i]+"\""); + } + + bru.setEnvVar("roles", 1); +} + tests { test("should ping pong", function() { const data = res.getBody(); diff --git a/packages/bruno-tests/collection/query.bru b/packages/bruno-tests/collection/query.bru new file mode 100644 index 0000000000..cf0d58bb6e --- /dev/null +++ b/packages/bruno-tests/collection/query.bru @@ -0,0 +1,78 @@ +meta { + name: query + type: http + seq: 2 +} + +get { + url: {{host}}/query?foo={{pong}} + body: none + auth: none +} + +query { + foo: {{pong}} +} + +auth:awsv4 { + accessKeyId: a + secretAccessKey: b + sessionToken: c + service: d + region: e + profileName: f +} + +vars:pre-request { + m4: true + pong: pong +} + +assert { + res.status: eq 200 + ~res.body: eq {{pong}} +} + +script:pre-request { + const project = 'foo' + const roles = ('abc, xyz'.split(",")).map(element => { + return element.trim(); + }); + + var project_roles = []; + + for (let i in roles) { + project_roles.push("\""+project+"_"+roles[i]+"\""); + } + + bru.setVar("pong", JSON.stringify([1,2,3,4])); +} + +tests { + test("should ping pong", function() { + const data = res.getBody(); + expect(data).to.equal(bru.getVar("pong")); + }); +} + +docs { + # API Documentation + + ## Introduction + + Welcome to the API documentation for [Your API Name]. This document provides instructions on how to make requests to the API and covers available authentication methods. + + ## Authentication + + Before making requests to the API, you need to authenticate your application. [Your API Name] supports the following authentication methods: + + ### API Key + + To use API key authentication, include your API key in the request headers as follows: + + ```http + GET /api/endpoint + Host: api.example.com + Authorization: Bearer YOUR_API_KEY + +} diff --git a/packages/bruno-tests/package.json b/packages/bruno-tests/package.json index 0135eeb616..84ede3d622 100644 --- a/packages/bruno-tests/package.json +++ b/packages/bruno-tests/package.json @@ -27,6 +27,7 @@ "express-xml-bodyparser": "^0.3.0", "http-proxy": "^1.18.1", "js-yaml": "^4.1.0", + "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", "multer": "^1.4.5-lts.1" } diff --git a/packages/bruno-tests/src/auth/index.js b/packages/bruno-tests/src/auth/index.js index 84d93798e2..0b5dc7f630 100644 --- a/packages/bruno-tests/src/auth/index.js +++ b/packages/bruno-tests/src/auth/index.js @@ -4,7 +4,13 @@ const router = express.Router(); const authBearer = require('./bearer'); const authBasic = require('./basic'); const authCookie = require('./cookie'); +const authOAuth2Ropc = require('./oauth2/ropc'); +const authOAuth2AuthorizationCode = require('./oauth2/ac'); +const authOAuth2Cc = require('./oauth2/cc'); +router.use('/oauth2/ropc', authOAuth2Ropc); +router.use('/oauth2/ac', authOAuth2AuthorizationCode); +router.use('/oauth2/cc', authOAuth2Cc); router.use('/bearer', authBearer); router.use('/basic', authBasic); router.use('/cookie', authCookie); diff --git a/packages/bruno-tests/src/auth/oauth2/ac.js b/packages/bruno-tests/src/auth/oauth2/ac.js new file mode 100644 index 0000000000..840c2a7788 --- /dev/null +++ b/packages/bruno-tests/src/auth/oauth2/ac.js @@ -0,0 +1,141 @@ +const express = require('express'); +const router = express.Router(); +const crypto = require('crypto'); +const clients = [ + { + client_id: 'client_id_1', + client_secret: 'client_secret_1', + redirect_uri: 'http://localhost:3001/callback' + } +]; + +const authCodes = []; + +const tokens = []; + +function generateUniqueString() { + return crypto.randomBytes(16).toString('hex'); +} + +router.get('/authorize', (req, res) => { + const { response_type, client_id, redirect_uri } = req.query; + if (response_type !== 'code') { + return res.status(401).json({ error: 'Invalid Response type, expected "code"' }); + } + + const client = clients.find((c) => c.client_id === client_id); + + if (!client) { + return res.status(401).json({ error: 'Invalid client' }); + } + + if (!redirect_uri) { + return res.status(401).json({ error: 'Invalid redirect URI' }); + } + + const authorization_code = generateUniqueString(); + authCodes.push({ + authCode: authorization_code, + client_id, + redirect_uri + }); + + const redirectUrl = `${redirect_uri}?code=${authorization_code}`; + + const _res = ` + + + + + + + `; + + res.send(_res); +}); + +// Handle the authorization callback +router.get('/callback', (req, res) => { + const { code } = req.query; + + // Check if the authCode is valid. + const storedAuthCode = authCodes.find((t) => t.authCode === code); + + if (!storedAuthCode) { + return res.status(401).json({ error: 'Invalid Authorization Code' }); + } + + return res.json({ message: 'Authorization successful', storedAuthCode }); +}); + +router.post('/token', (req, res) => { + let grant_type, code, redirect_uri, client_id, client_secret; + if (req?.body?.grant_type) { + grant_type = req?.body?.grant_type; + code = req?.body?.code; + redirect_uri = req?.body?.redirect_uri; + client_id = req?.body?.client_id; + client_secret = req?.body?.client_secret; + } + if (req?.headers?.grant_type) { + grant_type = req?.headers?.grant_type; + code = req?.headers?.code; + redirect_uri = req?.headers?.redirect_uri; + client_id = req?.headers?.client_id; + client_secret = req?.headers?.client_secret; + } + + if (grant_type !== 'authorization_code') { + return res.status(401).json({ error: 'Invalid Grant Type' }); + } + + // const client = clients.find((c) => c.client_id === client_id && c.client_secret === client_secret); + // if (!client) { + // return res.status(401).json({ error: 'Invalid client credentials' }); + // } + + const storedAuthCode = authCodes.find((t) => t.authCode === code); + + if (!storedAuthCode) { + return res.status(401).json({ error: 'Invalid Authorization Code' }); + } + + const accessToken = generateUniqueString(); + tokens.push({ + accessToken: accessToken, + client_id + }); + + res.json({ access_token: accessToken }); +}); + +router.post('/resource', (req, res) => { + try { + const { token } = req.query; + const storedToken = tokens.find((t) => t.accessToken === token); + if (!storedToken) { + return res.status(401).json({ error: 'Invalid Access Token' }); + } + return res.json({ resource: { name: 'foo', email: 'foo@bar.com' } }); + } catch (err) { + return res.status(401).json({ error: 'Corrupt Access Token' }); + } +}); + +module.exports = router; diff --git a/packages/bruno-tests/src/auth/oauth2/cc.js b/packages/bruno-tests/src/auth/oauth2/cc.js new file mode 100644 index 0000000000..dcaee30270 --- /dev/null +++ b/packages/bruno-tests/src/auth/oauth2/cc.js @@ -0,0 +1,62 @@ +const express = require('express'); +const router = express.Router(); +const crypto = require('crypto'); +const clients = [ + { + client_id: 'client_id_1', + client_secret: 'client_secret_1' + } +]; + +const tokens = []; + +function generateUniqueString() { + return crypto.randomBytes(16).toString('hex'); +} + +router.post('/token', (req, res) => { + let grant_type, client_id, client_secret; + if (req?.body?.grant_type) { + grant_type = req?.body?.grant_type; + client_id = req?.body?.client_id; + client_secret = req?.body?.client_secret; + } else if (req?.headers?.grant_type) { + grant_type = req?.headers?.grant_type; + client_id = req?.headers?.client_id; + client_secret = req?.headers?.client_secret; + } + + if (grant_type !== 'client_credentials') { + return res.status(401).json({ error: 'Invalid Grant Type, expected "client_credentials"' }); + } + + const client = clients.find((c) => c.client_id == client_id && c.client_secret == client_secret); + + if (!client) { + return res.status(401).json({ error: 'Invalid client' }); + } + + const token = generateUniqueString(); + tokens.push({ + token, + client_id, + client_secret + }); + + return res.json({ message: 'Authenticated successfully', access_token: token }); +}); + +router.get('/resource', (req, res) => { + try { + const { token } = req.query; + const storedToken = tokens.find((t) => t.token === token); + if (!storedToken) { + return res.status(401).json({ error: 'Invalid Access Token' }); + } + return res.json({ resource: { name: 'foo', email: 'foo@bar.com' } }); + } catch (err) { + return res.status(401).json({ error: 'Corrupt Access Token' }); + } +}); + +module.exports = router; diff --git a/packages/bruno-tests/src/auth/oauth2/ropc.js b/packages/bruno-tests/src/auth/oauth2/ropc.js new file mode 100644 index 0000000000..84bb979a75 --- /dev/null +++ b/packages/bruno-tests/src/auth/oauth2/ropc.js @@ -0,0 +1,59 @@ +const express = require('express'); +const router = express.Router(); +const jwt = require('jsonwebtoken'); + +const users = [ + { + username: 'foo', + password: 'bar' + } +]; + +// P +// { +// grant_type: 'password', +// username: 'foo', +// password: 'bar' +// } + +// I +// { +// grant_type: 'password', +// username: 'foo', +// password: 'bar', +// client_id: 'client_id_1', +// client_secret: 'client_secret_1' +// } +router.post('/token', (req, res) => { + const { grant_type, username, password, client_id, client_secret } = req.body; + + if (grant_type !== 'password') { + return res.status(401).json({ error: 'Invalid Grant Type' }); + } + + const user = users.find((u) => u.username == username && u.password == password); + + if (!user) { + return res.status(401).json({ error: 'Invalid user credentials' }); + } + var token = jwt.sign({ username, password }, 'bruno'); + return res.json({ message: 'Authorization successful', access_token: token }); +}); + +router.post('/resource', (req, res) => { + try { + const tokenString = req.header('Authorization'); + const token = tokenString.split(' ')[1]; + var decodedJwt = jwt.verify(token, 'bruno'); + const { username, password } = decodedJwt; + const user = users.find((u) => u.username === username && u.password === password); + if (!user) { + return res.status(401).json({ error: 'Invalid token' }); + } + return res.json({ resource: { name: 'foo', email: 'foo@bar.com' } }); + } catch (err) { + return res.status(401).json({ error: 'Corrupt token' }); + } +}); + +module.exports = router; diff --git a/packages/bruno-tests/src/index.js b/packages/bruno-tests/src/index.js index 286cfdad67..9ba6e31702 100644 --- a/packages/bruno-tests/src/index.js +++ b/packages/bruno-tests/src/index.js @@ -5,7 +5,7 @@ const cors = require('cors'); const multer = require('multer'); const app = new express(); -const port = process.env.PORT || 80; +const port = process.env.PORT || 8080; const upload = multer(); app.use(cors());