diff --git a/.env.example b/.env.example index 932846bfe..169e6db0d 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,13 @@ # Keys # Required key for platform encryption/decryption ops -ENCRYPTION_KEY=replace_with_lengthy_secure_hex +ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 # JWT # Required secrets to sign JWT tokens -JWT_SIGNUP_SECRET=replace_with_lengthy_secure_hex -JWT_REFRESH_SECRET=replace_with_lengthy_secure_hex -JWT_AUTH_SECRET=replace_with_lengthy_secure_hex -JWT_SERVICE_SECRET=replace_with_lengthy_secure_hex +JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a +JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff +JWT_AUTH_SECRET=4be6ba5602e0fa0ac6ac05c3cd4d247f +JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200 # JWT lifetime # Optional lifetimes for JWT tokens expressed in seconds or a string @@ -34,15 +34,12 @@ MONGO_PASSWORD=example SITE_URL=http://localhost:8080 # Mail/SMTP -# Required to send emails -# By default, SMTP_HOST is set to smtp.gmail.com, SMTP_PORT is set to 587, SMTP_TLS is set to false, and SMTP_FROM_NAME is set to Infisical -SMTP_HOST=smtp.gmail.com -# If STARTTLS is supported, the connection will be upgraded to TLS when SMTP_SECURE is set to false -SMTP_SECURE=false +SMTP_HOST= # required +SMTP_USERNAME= # required +SMTP_PASSWORD= # required SMTP_PORT=587 -SMTP_USERNAME= -SMTP_PASSWORD= -SMTP_FROM_ADDRESS= +SMTP_SECURE=false +SMTP_FROM_ADDRESS= # required SMTP_FROM_NAME=Infisical # Integration diff --git a/README.md b/README.md index 91bb67d5c..3851b3c53 100644 --- a/README.md +++ b/README.md @@ -321,4 +321,4 @@ Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of - + diff --git a/backend/environment.d.ts b/backend/environment.d.ts index 33034fdd5..291a33550 100644 --- a/backend/environment.d.ts +++ b/backend/environment.d.ts @@ -14,6 +14,8 @@ declare global { JWT_SIGNUP_SECRET: string; MONGO_URL: string; NODE_ENV: 'development' | 'staging' | 'testing' | 'production'; + VERBOSE_ERROR_OUTPUT: string; + LOKI_HOST: string; CLIENT_ID_HEROKU: string; CLIENT_ID_VERCEL: string; CLIENT_ID_NETLIFY: string; diff --git a/backend/package-lock.json b/backend/package-lock.json index 42c8205bd..85b168c1e 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -37,7 +37,9 @@ "stripe": "^10.7.0", "tweetnacl": "^1.0.3", "tweetnacl-util": "^0.15.1", - "typescript": "^4.9.3" + "typescript": "^4.9.3", + "winston": "^3.8.2", + "winston-loki": "^6.0.6" }, "devDependencies": { "@jest/globals": "^29.3.1", @@ -2003,6 +2005,14 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -2015,6 +2025,16 @@ "node": ">=12" } }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -2567,6 +2587,21 @@ "maxmind": "^4.2.0" } }, + "node_modules/@napi-rs/snappy-darwin-arm64": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-arm64/-/snappy-darwin-arm64-7.1.1.tgz", + "integrity": "sha512-3LZyoAw3Qa5F7sCCTkSkhmGlydwUKU6L3Jl46eKHO2Ctm8Gcjxww6T7MfwlwGZ6JqAM6d1d++WLzUZPCGXVmag==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2758,6 +2793,60 @@ "@maxmind/geoip2-node": "^3.4.0" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@sentry/node": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.19.0.tgz", @@ -3075,6 +3164,11 @@ "resolved": "https://registry.npmjs.org/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz", "integrity": "sha512-BqI9B92u+cM3ccp8mpHf+HzJ8fBlRwdmyd6+fz3p99m3V6ifT5O3zmOMi612PGkpeFeG/G6loxUnzlDNhfjPSA==" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "node_modules/@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -3095,6 +3189,12 @@ "@types/node": "*" } }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -3560,6 +3660,19 @@ "node": ">=0.8" } }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3835,6 +3948,17 @@ "node": ">=6.9.0" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -4059,6 +4183,15 @@ "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4074,8 +4207,38 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } }, "node_modules/combined-stream": { "version": "1.0.8", @@ -4425,6 +4588,11 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -4921,6 +5089,11 @@ "bser": "2.1.1" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5018,6 +5191,11 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "node_modules/follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -5604,7 +5782,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -6152,6 +6329,7 @@ "@jest/transform": "^29.3.1", "@jest/types": "^29.3.1", "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.3.1", @@ -6403,6 +6581,11 @@ "node": ">=6" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6476,6 +6659,23 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "dependencies": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -6939,6 +7139,9 @@ }, "bin": { "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" } }, "node_modules/normalize-path": { @@ -9563,6 +9766,14 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -9810,9 +10021,9 @@ } }, "node_modules/posthog-node": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.2.2.tgz", - "integrity": "sha512-aXYe/D+28kF63W8Cz53t09ypEORz+ULeDCahdAqhVrRm2scbOXFbtnn0GGhvMpYe45grepLKuwui9KxrZ2ZuMw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.2.3.tgz", + "integrity": "sha512-dYlLZhrDus+uRov/Hh+EiRlMoMhRKchNjNa7mNE2iWmKg/ryOTipf0XYKS9UKdki7aU1NzWFhnLe11HF615XuA==", "dependencies": { "axios": "^0.27.0" }, @@ -9877,6 +10088,31 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -10184,6 +10420,14 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10332,6 +10576,19 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/simple-update-notifier": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", @@ -10377,6 +10634,34 @@ "npm": ">= 3.0.0" } }, + "node_modules/snappy": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/snappy/-/snappy-7.1.1.tgz", + "integrity": "sha512-mL7GGPJ+WdsaFT5aR/uEqCq8cPg2VbhyifDEP7AeqIVDsAC8LBGYbZP1Qzoa2Ym84OW7JEQXqIpwqFp1EQw5BA==", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/snappy-android-arm-eabi": "7.1.1", + "@napi-rs/snappy-android-arm64": "7.1.1", + "@napi-rs/snappy-darwin-arm64": "7.1.1", + "@napi-rs/snappy-darwin-x64": "7.1.1", + "@napi-rs/snappy-freebsd-x64": "7.1.1", + "@napi-rs/snappy-linux-arm-gnueabihf": "7.1.1", + "@napi-rs/snappy-linux-arm64-gnu": "7.1.1", + "@napi-rs/snappy-linux-arm64-musl": "7.1.1", + "@napi-rs/snappy-linux-x64-gnu": "7.1.1", + "@napi-rs/snappy-linux-x64-musl": "7.1.1", + "@napi-rs/snappy-win32-arm64-msvc": "7.1.1", + "@napi-rs/snappy-win32-ia32-msvc": "7.1.1", + "@napi-rs/snappy-win32-x64-msvc": "7.1.1" + } + }, "node_modules/socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -10431,6 +10716,14 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -10656,6 +10949,11 @@ "node": ">=8" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10729,6 +11027,11 @@ "node": ">=12" } }, + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, "node_modules/ts-jest": { "version": "29.0.3", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz", @@ -11100,6 +11403,54 @@ "node": ">= 8" } }, + "node_modules/winston": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", + "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "dependencies": { + "@colors/colors": "1.5.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-loki": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/winston-loki/-/winston-loki-6.0.6.tgz", + "integrity": "sha512-cll+nv5T/b9uJXqca0N2WKL1JJNuJND9E6WOOAuSGkZ44L9VQ/QK9F+/5VKbv6LIP9p0nvPSOYxtACCDb/9iWw==", + "dependencies": { + "async-exit-hook": "2.0.1", + "btoa": "^1.2.1", + "protobufjs": "^6.8.8", + "winston-transport": "^4.3.0" + }, + "optionalDependencies": { + "snappy": "7.1.1" + } + }, + "node_modules/winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -12957,6 +13308,11 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -12966,6 +13322,16 @@ "@jridgewell/trace-mapping": "0.3.9" } }, + "@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -13413,6 +13779,12 @@ "maxmind": "^4.2.0" } }, + "@napi-rs/snappy-darwin-arm64": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-arm64/-/snappy-darwin-arm64-7.1.1.tgz", + "integrity": "sha512-3LZyoAw3Qa5F7sCCTkSkhmGlydwUKU6L3Jl46eKHO2Ctm8Gcjxww6T7MfwlwGZ6JqAM6d1d++WLzUZPCGXVmag==", + "optional": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -13560,6 +13932,60 @@ "@maxmind/geoip2-node": "^3.4.0" } }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "@sentry/node": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.19.0.tgz", @@ -13857,6 +14283,11 @@ "resolved": "https://registry.npmjs.org/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz", "integrity": "sha512-BqI9B92u+cM3ccp8mpHf+HzJ8fBlRwdmyd6+fz3p99m3V6ifT5O3zmOMi612PGkpeFeG/G6loxUnzlDNhfjPSA==" }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -13877,6 +14308,12 @@ "@types/node": "*" } }, + "@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -14204,6 +14641,16 @@ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -14414,6 +14861,11 @@ "buffer": "^5.6.0" } }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -14565,6 +15017,30 @@ "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + }, + "dependencies": { + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + } + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -14577,8 +15053,25 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "requires": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } }, "combined-stream": { "version": "1.0.8", @@ -14846,6 +15339,11 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -15233,6 +15731,11 @@ "bser": "2.1.1" } }, + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -15311,6 +15814,11 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -15711,8 +16219,7 @@ "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, "isexe": { "version": "2.0.0", @@ -16129,6 +16636,7 @@ "@jest/transform": "^29.3.1", "@jest/types": "^29.3.1", "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.3.1", @@ -16336,6 +16844,11 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -16397,6 +16910,23 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "requires": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -18578,6 +19108,14 @@ "wrappy": "1" } }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, "onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -18752,9 +19290,9 @@ } }, "posthog-node": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.2.2.tgz", - "integrity": "sha512-aXYe/D+28kF63W8Cz53t09ypEORz+ULeDCahdAqhVrRm2scbOXFbtnn0GGhvMpYe45grepLKuwui9KxrZ2ZuMw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.2.3.tgz", + "integrity": "sha512-dYlLZhrDus+uRov/Hh+EiRlMoMhRKchNjNa7mNE2iWmKg/ryOTipf0XYKS9UKdki7aU1NzWFhnLe11HF615XuA==", "requires": { "axios": "^0.27.0" }, @@ -18805,6 +19343,26 @@ "sisteransi": "^1.0.5" } }, + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -18999,6 +19557,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -19124,6 +19687,21 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, "simple-update-notifier": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", @@ -19158,6 +19736,27 @@ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, + "snappy": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/snappy/-/snappy-7.1.1.tgz", + "integrity": "sha512-mL7GGPJ+WdsaFT5aR/uEqCq8cPg2VbhyifDEP7AeqIVDsAC8LBGYbZP1Qzoa2Ym84OW7JEQXqIpwqFp1EQw5BA==", + "optional": true, + "requires": { + "@napi-rs/snappy-android-arm-eabi": "7.1.1", + "@napi-rs/snappy-android-arm64": "7.1.1", + "@napi-rs/snappy-darwin-arm64": "7.1.1", + "@napi-rs/snappy-darwin-x64": "7.1.1", + "@napi-rs/snappy-freebsd-x64": "7.1.1", + "@napi-rs/snappy-linux-arm-gnueabihf": "7.1.1", + "@napi-rs/snappy-linux-arm64-gnu": "7.1.1", + "@napi-rs/snappy-linux-arm64-musl": "7.1.1", + "@napi-rs/snappy-linux-x64-gnu": "7.1.1", + "@napi-rs/snappy-linux-x64-musl": "7.1.1", + "@napi-rs/snappy-win32-arm64-msvc": "7.1.1", + "@napi-rs/snappy-win32-ia32-msvc": "7.1.1", + "@napi-rs/snappy-win32-x64-msvc": "7.1.1" + } + }, "socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -19202,6 +19801,11 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" + }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -19367,6 +19971,11 @@ "minimatch": "^3.0.4" } }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -19422,6 +20031,11 @@ "punycode": "^2.1.1" } }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, "ts-jest": { "version": "29.0.3", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz", @@ -19663,6 +20277,46 @@ "isexe": "^2.0.0" } }, + "winston": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", + "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "requires": { + "@colors/colors": "1.5.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + } + }, + "winston-loki": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/winston-loki/-/winston-loki-6.0.6.tgz", + "integrity": "sha512-cll+nv5T/b9uJXqca0N2WKL1JJNuJND9E6WOOAuSGkZ44L9VQ/QK9F+/5VKbv6LIP9p0nvPSOYxtACCDb/9iWw==", + "requires": { + "async-exit-hook": "2.0.1", + "btoa": "^1.2.1", + "protobufjs": "^6.8.8", + "snappy": "7.1.1", + "winston-transport": "^4.3.0" + } + }, + "winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "requires": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/backend/package.json b/backend/package.json index 266bb7b39..d1a03a74c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -28,7 +28,9 @@ "stripe": "^10.7.0", "tweetnacl": "^1.0.3", "tweetnacl-util": "^0.15.1", - "typescript": "^4.9.3" + "typescript": "^4.9.3", + "winston": "^3.8.2", + "winston-loki": "^6.0.6" }, "name": "infisical-api", "version": "1.0.0", diff --git a/backend/src/app.ts b/backend/src/app.ts index fa11b5f1c..e49551a91 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1,5 +1,5 @@ -/* eslint-disable no-console */ +import { patchRouterParam } from './utils/patchAsyncRoutes'; import express from 'express'; import helmet from 'helmet'; import cors from 'cors'; @@ -29,6 +29,12 @@ import { integration as integrationRouter, integrationAuth as integrationAuthRouter } from './routes'; +import { getLogger } from './utils/logger'; +import { RouteNotFoundError } from './utils/errors'; +import { requestErrorHandler } from './middleware/requestErrorHandler'; + +//* Patch Async route params to handle Promise Rejections +patchRouterParam() export const app = express(); @@ -69,6 +75,17 @@ app.use('/api/v1/stripe', stripeRouter); app.use('/api/v1/integration', integrationRouter); app.use('/api/v1/integration-auth', integrationAuthRouter); + +//* Handle unrouted requests and respond with proper error message as well as status code +app.use((req, res, next)=>{ + if(res.headersSent) return next(); + next(RouteNotFoundError({message: `The requested source '(${req.method})${req.url}' was not found`})) +}) + +//* Error Handling Middleware (must be after all routing logic) +app.use(requestErrorHandler) + + export const server = app.listen(PORT, () => { - console.log(`Listening on PORT ${[PORT]}`); + getLogger("backend-main").info(`Server started listening at port ${PORT}`) }); diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 43a9026ac..dfbc2111c 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,6 +10,8 @@ const JWT_SIGNUP_LIFETIME = process.env.JWT_SIGNUP_LIFETIME! || '15m'; const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!; const MONGO_URL = process.env.MONGO_URL!; const NODE_ENV = process.env.NODE_ENV! || 'production'; +const VERBOSE_ERROR_OUTPUT = process.env.VERBOSE_ERROR_OUTPUT! === 'true' && true; +const LOKI_HOST = process.env.LOKI_HOST || undefined; const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!; const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!; const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!; @@ -25,9 +27,9 @@ const POSTHOG_PROJECT_API_KEY = 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; const SENTRY_DSN = process.env.SENTRY_DSN!; const SITE_URL = process.env.SITE_URL!; -const SMTP_HOST = process.env.SMTP_HOST! || 'smtp.gmail.com'; -const SMTP_SECURE = process.env.SMTP_SECURE! || false; -const SMTP_PORT = process.env.SMTP_PORT! || 587; +const SMTP_HOST = process.env.SMTP_HOST!; +const SMTP_SECURE = process.env.SMTP_SECURE! === 'true' || false; +const SMTP_PORT = parseInt(process.env.SMTP_PORT!) || 587; const SMTP_USERNAME = process.env.SMTP_USERNAME!; const SMTP_PASSWORD = process.env.SMTP_PASSWORD!; const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS!; @@ -53,6 +55,8 @@ export { JWT_SIGNUP_SECRET, MONGO_URL, NODE_ENV, + VERBOSE_ERROR_OUTPUT, + LOKI_HOST, CLIENT_ID_HEROKU, CLIENT_ID_VERCEL, CLIENT_ID_NETLIFY, diff --git a/backend/src/helpers/integration.ts b/backend/src/helpers/integration.ts index ccfd72a53..d92156ece 100644 --- a/backend/src/helpers/integration.ts +++ b/backend/src/helpers/integration.ts @@ -2,18 +2,18 @@ import * as Sentry from '@sentry/node'; import { Bot, Integration, - IIntegration, IntegrationAuth, - IIntegrationAuth } from '../models'; import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations'; -import { BotService, IntegrationService } from '../services'; +import { BotService } from '../services'; import { ENV_DEV, EVENT_PUSH_SECRETS, INTEGRATION_VERCEL, INTEGRATION_NETLIFY } from '../variables'; +import { UnauthorizedRequestError } from '../utils/errors'; +import RequestError from '../utils/requestError'; interface Update { workspace: string; @@ -176,12 +176,13 @@ const syncIntegrationsHelper = async ({ */ const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => { let refreshToken; + try { const integrationAuth = await IntegrationAuth .findById(integrationAuthId) .select('+refreshCiphertext +refreshIV +refreshTag'); - if (!integrationAuth) throw new Error('Failed to find integration auth'); + if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}); refreshToken = await BotService.decryptSymmetric({ workspaceId: integrationAuth.workspace.toString(), @@ -193,7 +194,10 @@ const syncIntegrationsHelper = async ({ } catch (err) { Sentry.setUser(null); Sentry.captureException(err); - throw new Error('Failed to get integration refresh token'); + if(err instanceof RequestError) + throw err + else + throw new Error('Failed to get integration refresh token'); } return refreshToken; @@ -209,12 +213,13 @@ const syncIntegrationsHelper = async ({ */ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => { let accessToken; + try { const integrationAuth = await IntegrationAuth .findById(integrationAuthId) .select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext'); - if (!integrationAuth) throw new Error('Failed to find integration auth'); + if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}); accessToken = await BotService.decryptSymmetric({ workspaceId: integrationAuth.workspace.toString(), @@ -240,7 +245,10 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati } catch (err) { Sentry.setUser(null); Sentry.captureException(err); - throw new Error('Failed to get integration access token'); + if(err instanceof RequestError) + throw err + else + throw new Error('Failed to get integration access token'); } return accessToken; diff --git a/backend/src/helpers/membership.ts b/backend/src/helpers/membership.ts index 1ff542f2a..b06460cde 100644 --- a/backend/src/helpers/membership.ts +++ b/backend/src/helpers/membership.ts @@ -21,6 +21,7 @@ const validateMembership = async ({ }) => { let membership; + //TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors try { membership = await Membership.findOne({ user: userId, diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index dafddc785..cb0ff84e0 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -9,8 +9,7 @@ import { INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_GITHUB_TOKEN_URL, - INTEGRATION_GITHUB_API_URL, - ACTION_PUSH_TO_HEROKU + INTEGRATION_GITHUB_API_URL } from '../variables'; import { SITE_URL, diff --git a/backend/src/integrations/refresh.ts b/backend/src/integrations/refresh.ts index 8ddb6a651..ea232f1e5 100644 --- a/backend/src/integrations/refresh.ts +++ b/backend/src/integrations/refresh.ts @@ -53,6 +53,7 @@ const exchangeRefreshHeroku = async ({ refreshToken: string; }) => { let accessToken; + //TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors try { const res = await axios.post( INTEGRATION_HEROKU_TOKEN_URL, diff --git a/backend/src/middleware/requestErrorHandler.ts b/backend/src/middleware/requestErrorHandler.ts new file mode 100644 index 000000000..36f1dce49 --- /dev/null +++ b/backend/src/middleware/requestErrorHandler.ts @@ -0,0 +1,29 @@ +import { ErrorRequestHandler } from "express"; + +import * as Sentry from '@sentry/node'; +import { InternalServerError } from "../utils/errors"; +import { getLogger } from "../utils/logger"; +import RequestError, { LogLevel } from "../utils/requestError"; + + +export const requestErrorHandler: ErrorRequestHandler = (error: RequestError|Error, req, res, next) => { + if(res.headersSent) return next(); + //TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError + if(!(error instanceof RequestError)){ + error = InternalServerError({context: {exception: error.message}, stack: error.stack}) + getLogger('backend-main').log((error).levelName.toLowerCase(), (error).message) + } + + //* Set Sentry user identification if req.user is populated + if(req.user !== undefined && req.user !== null){ + Sentry.setUser({ email: req.user.email }) + } + //* Only sent error to Sentry if LogLevel is one of the following level 'ERROR', 'EMERGENCY' or 'CRITICAL' + //* with this we will eliminate false-positive errors like 'BadRequestError', 'UnauthorizedRequestError' and so on + if([LogLevel.ERROR, LogLevel.EMERGENCY, LogLevel.CRITICAL].includes((error).level)){ + Sentry.captureException(error) + } + + res.status((error).statusCode).json((error).format(req)) + next() +} \ No newline at end of file diff --git a/backend/src/middleware/requireAuth.ts b/backend/src/middleware/requireAuth.ts index a6bd79073..d917d362a 100644 --- a/backend/src/middleware/requireAuth.ts +++ b/backend/src/middleware/requireAuth.ts @@ -1,8 +1,8 @@ import jwt from 'jsonwebtoken'; import { Request, Response, NextFunction } from 'express'; -import * as Sentry from '@sentry/node'; import { User } from '../models'; import { JWT_AUTH_SECRET } from '../config'; +import { AccountNotFoundError, BadRequestError, UnauthorizedRequestError } from '../utils/errors'; declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -20,32 +20,25 @@ declare module 'jsonwebtoken' { */ const requireAuth = async (req: Request, res: Response, next: NextFunction) => { // JWT authentication middleware - try { - if (!req.headers?.authorization) - throw new Error('Failed to locate authorization header'); + const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null] + if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`})) + if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(BadRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`})) + if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) - const token = req.headers.authorization.split(' ')[1]; - const decodedToken = ( - jwt.verify(token, JWT_AUTH_SECRET) - ); + const decodedToken = ( + jwt.verify(AUTH_TOKEN_VALUE, JWT_AUTH_SECRET) + ); - const user = await User.findOne({ - _id: decodedToken.userId - }).select('+publicKey'); + const user = await User.findOne({ + _id: decodedToken.userId + }).select('+publicKey'); - if (!user) throw new Error('Failed to authenticate unfound user'); - if (!user?.publicKey) - throw new Error('Failed to authenticate not fully set up account'); + if (!user) return next(AccountNotFoundError({message: 'Failed to locate User account'})) + if (!user?.publicKey) + return next(UnauthorizedRequestError({message: 'Unable to authenticate due to partially set up account'})) - req.user = user; - return next(); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(401).send({ - error: 'Failed to authenticate user. Try logging in' - }); - } + req.user = user; + return next(); }; export default requireAuth; diff --git a/backend/src/middleware/requireBotAuth.ts b/backend/src/middleware/requireBotAuth.ts index 6c5a3820a..e39f0d1b5 100644 --- a/backend/src/middleware/requireBotAuth.ts +++ b/backend/src/middleware/requireBotAuth.ts @@ -1,7 +1,7 @@ -import * as Sentry from '@sentry/node'; import { Request, Response, NextFunction } from 'express'; import { Bot } from '../models'; import { validateMembership } from '../helpers/membership'; +import { AccountNotFoundError } from '../utils/errors'; type req = 'params' | 'body' | 'query'; @@ -15,30 +15,22 @@ const requireBotAuth = ({ location?: req; }) => { return async (req: Request, res: Response, next: NextFunction) => { - try { - const bot = await Bot.findOne({ _id: req[location].botId }); - - if (!bot) { - throw new Error('Failed to find bot'); - } - - await validateMembership({ - userId: req.user._id.toString(), - workspaceId: bot.workspace.toString(), - acceptedRoles, - acceptedStatuses - }); - - req.bot = bot; - - next(); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(401).send({ - error: 'Failed bot authorization' - }); + const bot = await Bot.findOne({ _id: req[location].botId }); + + if (!bot) { + return next(AccountNotFoundError({message: 'Failed to locate Bot account'})) } + + await validateMembership({ + userId: req.user._id.toString(), + workspaceId: bot.workspace.toString(), + acceptedRoles, + acceptedStatuses + }); + + req.bot = bot; + + next(); } } diff --git a/backend/src/middleware/requireIntegrationAuth.ts b/backend/src/middleware/requireIntegrationAuth.ts index fe653dbc0..4389028ab 100644 --- a/backend/src/middleware/requireIntegrationAuth.ts +++ b/backend/src/middleware/requireIntegrationAuth.ts @@ -1,8 +1,8 @@ -import * as Sentry from '@sentry/node'; import { Request, Response, NextFunction } from 'express'; -import { Bot, Integration, IntegrationAuth, Membership } from '../models'; +import { Integration, IntegrationAuth } from '../models'; import { IntegrationService } from '../services'; import { validateMembership } from '../helpers/membership'; +import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/errors'; /** * Validate if user on request is a member of workspace with proper roles associated @@ -21,48 +21,40 @@ const requireIntegrationAuth = ({ return async (req: Request, res: Response, next: NextFunction) => { // integration authorization middleware - try { - const { integrationId } = req.params; + const { integrationId } = req.params; - // validate integration accessibility - const integration = await Integration.findOne({ - _id: integrationId - }); + // validate integration accessibility + const integration = await Integration.findOne({ + _id: integrationId + }); - if (!integration) { - throw new Error('Failed to find integration'); - } - - await validateMembership({ - userId: req.user._id.toString(), - workspaceId: integration.workspace.toString(), - acceptedRoles, - acceptedStatuses - }); - - const integrationAuth = await IntegrationAuth.findOne({ - _id: integration.integrationAuth - }).select( - '+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt' - ); - - if (!integrationAuth) { - throw new Error('Failed to find integration authorization'); - } - - req.integration = integration; - req.accessToken = await IntegrationService.getIntegrationAuthAccess({ - integrationAuthId: integrationAuth._id.toString() - }); - - return next(); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(401).send({ - error: 'Failed integration authorization' - }); + if (!integration) { + return next(IntegrationNotFoundError({message: 'Failed to locate Integration'})) } + + await validateMembership({ + userId: req.user._id.toString(), + workspaceId: integration.workspace.toString(), + acceptedRoles, + acceptedStatuses + }); + + const integrationAuth = await IntegrationAuth.findOne({ + _id: integration.integrationAuth + }).select( + '+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt' + ); + + if (!integrationAuth) { + return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'})) + } + + req.integration = integration; + req.accessToken = await IntegrationService.getIntegrationAuthAccess({ + integrationAuthId: integrationAuth._id.toString() + }); + + return next(); }; }; diff --git a/backend/src/middleware/requireIntegrationAuthorizationAuth.ts b/backend/src/middleware/requireIntegrationAuthorizationAuth.ts index ed44ffec5..278716e60 100644 --- a/backend/src/middleware/requireIntegrationAuthorizationAuth.ts +++ b/backend/src/middleware/requireIntegrationAuthorizationAuth.ts @@ -3,6 +3,7 @@ import { Request, Response, NextFunction } from 'express'; import { IntegrationAuth } from '../models'; import { IntegrationService } from '../services'; import { validateMembership } from '../helpers/membership'; +import { UnauthorizedRequestError } from '../utils/errors'; /** * Validate if user on request is a member of workspace with proper roles associated @@ -22,41 +23,33 @@ const requireIntegrationAuthorizationAuth = ({ attachAccessToken?: boolean; }) => { return async (req: Request, res: Response, next: NextFunction) => { - try { - const { integrationAuthId } = req.params; + const { integrationAuthId } = req.params; - const integrationAuth = await IntegrationAuth.findOne({ - _id: integrationAuthId - }).select( - '+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt' - ); + const integrationAuth = await IntegrationAuth.findOne({ + _id: integrationAuthId + }).select( + '+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt' + ); - if (!integrationAuth) { - throw new Error('Failed to find integration authorization'); - } - - await validateMembership({ - userId: req.user._id.toString(), - workspaceId: integrationAuth.workspace.toString(), - acceptedRoles, - acceptedStatuses - }); + if (!integrationAuth) { + return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authorization credentials'})) + } + + await validateMembership({ + userId: req.user._id.toString(), + workspaceId: integrationAuth.workspace.toString(), + acceptedRoles, + acceptedStatuses + }); - req.integrationAuth = integrationAuth; - if (attachAccessToken) { - req.accessToken = await IntegrationService.getIntegrationAuthAccess({ - integrationAuthId: integrationAuth._id.toString() - }); - } - - return next(); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(401).send({ - error: 'Failed (authorization) integration authorizationt' + req.integrationAuth = integrationAuth; + if (attachAccessToken) { + req.accessToken = await IntegrationService.getIntegrationAuthAccess({ + integrationAuthId: integrationAuth._id.toString() }); } + + return next(); }; }; diff --git a/backend/src/middleware/requireOrganizationAuth.ts b/backend/src/middleware/requireOrganizationAuth.ts index c77bd312e..04542b429 100644 --- a/backend/src/middleware/requireOrganizationAuth.ts +++ b/backend/src/middleware/requireOrganizationAuth.ts @@ -1,6 +1,6 @@ -import * as Sentry from '@sentry/node'; import { Request, Response, NextFunction } from 'express'; import { IOrganization, MembershipOrg } from '../models'; +import { UnauthorizedRequestError, ValidationError } from '../utils/errors'; /** * Validate if user on request is a member with proper roles for organization @@ -19,35 +19,28 @@ const requireOrganizationAuth = ({ return async (req: Request, res: Response, next: NextFunction) => { // organization authorization middleware - try { - // validate organization membership - const membershipOrg = await MembershipOrg.findOne({ - user: req.user._id, - organization: req.params.organizationId - }).populate<{ organization: IOrganization }>('organization'); + // validate organization membership + const membershipOrg = await MembershipOrg.findOne({ + user: req.user._id, + organization: req.params.organizationId + }).populate<{ organization: IOrganization }>('organization'); - if (!membershipOrg) { - throw new Error('Failed to find organization membership'); - } - if (!acceptedRoles.includes(membershipOrg.role)) { - throw new Error('Failed to validate organization membership role'); - } - - if (!acceptedStatuses.includes(membershipOrg.status)) { - throw new Error('Failed to validate organization membership status'); - } - - req.membershipOrg = membershipOrg; - - return next(); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(401).send({ - error: 'Failed organization authorization' - }); + if (!membershipOrg) { + return next(UnauthorizedRequestError({message: "You're not a member of this Organization."})) } + //TODO is this important to validate? I mean is it possible to save wrong role to database or get wrong role from databse? - Zamion101 + if (!acceptedRoles.includes(membershipOrg.role)) { + return next(ValidationError({message: 'Failed to validate Organization Membership Role'})) + } + + if (!acceptedStatuses.includes(membershipOrg.status)) { + return next(ValidationError({message: 'Failed to validate Organization Membership Status'})) + } + + req.membershipOrg = membershipOrg; + + return next(); }; }; diff --git a/backend/src/middleware/requireServiceTokenAuth.ts b/backend/src/middleware/requireServiceTokenAuth.ts index d403e8fe5..94e8363ff 100644 --- a/backend/src/middleware/requireServiceTokenAuth.ts +++ b/backend/src/middleware/requireServiceTokenAuth.ts @@ -1,8 +1,8 @@ import jwt from 'jsonwebtoken'; import { Request, Response, NextFunction } from 'express'; -import * as Sentry from '@sentry/node'; import { ServiceToken } from '../models'; import { JWT_SERVICE_SECRET } from '../config'; +import { BadRequestError, UnauthorizedRequestError } from '../utils/errors'; declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -24,33 +24,27 @@ const requireServiceTokenAuth = async ( next: NextFunction ) => { // JWT service token middleware - try { - if (!req.headers?.authorization) - throw new Error('Failed to locate authorization header'); + + const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null] + if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`})) + //TODO: Determine what is the actual Token Type for Service Token Authentication (ex. Bearer) + //if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(UnauthorizedRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`})) + if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) - const token = req.headers.authorization.split(' ')[1]; + const decodedToken = ( + jwt.verify(AUTH_TOKEN_VALUE, JWT_SERVICE_SECRET) + ); - const decodedToken = ( - jwt.verify(token, JWT_SERVICE_SECRET) - ); + const serviceToken = await ServiceToken.findOne({ + _id: decodedToken.serviceTokenId + }) + .populate('user', '+publicKey') + .select('+encryptedKey +publicKey +nonce'); - const serviceToken = await ServiceToken.findOne({ - _id: decodedToken.serviceTokenId - }) - .populate('user', '+publicKey') - .select('+encryptedKey +publicKey +nonce'); + if (!serviceToken) return next(UnauthorizedRequestError({message: 'The service token does not match the record in the database'})) - if (!serviceToken) throw new Error('Failed to find service token'); - - req.serviceToken = serviceToken; - return next(); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(401).send({ - error: 'Failed to authenticate service token' - }); - } + req.serviceToken = serviceToken; + return next(); }; export default requireServiceTokenAuth; diff --git a/backend/src/middleware/requireSignupAuth.ts b/backend/src/middleware/requireSignupAuth.ts index 9384387ee..3318bd8d3 100644 --- a/backend/src/middleware/requireSignupAuth.ts +++ b/backend/src/middleware/requireSignupAuth.ts @@ -1,8 +1,8 @@ import jwt from 'jsonwebtoken'; import { Request, Response, NextFunction } from 'express'; -import * as Sentry from '@sentry/node'; import { User } from '../models'; import { JWT_SIGNUP_SECRET } from '../config'; +import { BadRequestError, UnauthorizedRequestError } from '../utils/errors'; declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -21,32 +21,24 @@ const requireSignupAuth = async ( ) => { // JWT (temporary) authentication middleware for complete signup - try { - if (!req.headers?.authorization) - throw new Error('Failed to locate authorization header'); + const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null] + if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`})) + if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(BadRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`})) + if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) + + const decodedToken = ( + jwt.verify(AUTH_TOKEN_VALUE, JWT_SIGNUP_SECRET) + ); - const token = req.headers.authorization.split(' ')[1]; - const decodedToken = ( - jwt.verify(token, JWT_SIGNUP_SECRET) - ); + const user = await User.findOne({ + _id: decodedToken.userId + }).select('+publicKey'); - const user = await User.findOne({ - _id: decodedToken.userId - }).select('+publicKey'); + if (!user) + return next(UnauthorizedRequestError({message: 'Unable to authenticate for User account completion. Try logging in again'})) - if (!user) - throw new Error('Failed to temporarily authenticate unfound user'); - - req.user = user; - return next(); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(401).send({ - error: - 'Failed to temporarily authenticate user for complete account. Try logging in' - }); - } + req.user = user; + return next(); }; export default requireSignupAuth; diff --git a/backend/src/middleware/requireWorkspaceAuth.ts b/backend/src/middleware/requireWorkspaceAuth.ts index a27611f2d..e5b8898f3 100644 --- a/backend/src/middleware/requireWorkspaceAuth.ts +++ b/backend/src/middleware/requireWorkspaceAuth.ts @@ -1,6 +1,6 @@ -import * as Sentry from '@sentry/node'; import { Request, Response, NextFunction } from 'express'; import { validateMembership } from '../helpers/membership'; +import { UnauthorizedRequestError } from '../utils/errors'; type req = 'params' | 'body' | 'query'; @@ -36,11 +36,7 @@ const requireWorkspaceAuth = ({ return next(); } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(401).send({ - error: 'Failed workspace authorization' - }); + return next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'})) } }; }; diff --git a/backend/src/middleware/validateRequest.ts b/backend/src/middleware/validateRequest.ts index 4dcafc13b..484b02cab 100644 --- a/backend/src/middleware/validateRequest.ts +++ b/backend/src/middleware/validateRequest.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from 'express'; -import * as Sentry from '@sentry/node'; import { validationResult } from 'express-validator'; +import { BadRequestError, UnauthorizedRequestError } from '../utils/errors'; /** * Validate intended inputs on [req] via express-validator @@ -15,16 +15,12 @@ const validate = (req: Request, res: Response, next: NextFunction) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); + return next(BadRequestError({context: {errors: errors.array}})) } return next(); } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - return res.status(401).send({ - error: "Looks like you're unauthenticated . Try logging in" - }); + return next(UnauthorizedRequestError({message: 'Unauthenticated requests are not allowed. Try logging in'})) } }; diff --git a/backend/src/services/PostHogClient.ts b/backend/src/services/PostHogClient.ts index 4096ac8ec..4ce0117f0 100644 --- a/backend/src/services/PostHogClient.ts +++ b/backend/src/services/PostHogClient.ts @@ -5,8 +5,16 @@ import { POSTHOG_PROJECT_API_KEY, TELEMETRY_ENABLED } from '../config'; +import { getLogger } from '../utils/logger'; -console.log('TELEMETRY_ENABLED: ', TELEMETRY_ENABLED); +if(TELEMETRY_ENABLED){ + getLogger("backend-main").info([ + "", + "Infisical collects telemetry data about general usage.", + "The data helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth for investors as we support Infisical as open-source software.", + "To opt out of telemetry, you can set `TELEMETRY_ENABLED=false` within the environment variables", + ].join('\n')) +} let postHogClient: any; if (NODE_ENV === 'production' && TELEMETRY_ENABLED) { diff --git a/backend/src/services/database.ts b/backend/src/services/database.ts index 5fc4955f1..85f39c1b2 100644 --- a/backend/src/services/database.ts +++ b/backend/src/services/database.ts @@ -1,10 +1,10 @@ -/* eslint-disable no-console */ import mongoose from 'mongoose'; +import { getLogger } from '../utils/logger'; export const initDatabase = (MONGO_URL: string) => { mongoose .connect(MONGO_URL) - .then(() => console.log('Successfully connected to DB')) - .catch((e) => console.log('Failed to connect to DB ', e)); + .then(() => getLogger("database").info("Database connection established")) + .catch((e) => getLogger("database").error(`Unable to establish Database connection due to the error.\n${e}`)); return mongoose.connection; }; diff --git a/backend/src/services/health.ts b/backend/src/services/health.ts index b4ab2a97b..9c441ba9d 100644 --- a/backend/src/services/health.ts +++ b/backend/src/services/health.ts @@ -1,10 +1,10 @@ -/* eslint-disable no-console */ import mongoose from 'mongoose'; import { createTerminus } from '@godaddy/terminus'; +import { getLogger } from '../utils/logger'; export const setUpHealthEndpoint = (server: T) => { const onSignal = () => { - console.log('Server is starting clean-up'); + getLogger('backend-main').info('Server is starting clean-up'); return Promise.all([ new Promise((resolve) => { if (mongoose.connection && mongoose.connection.readyState == 1) { diff --git a/backend/src/services/smtp.ts b/backend/src/services/smtp.ts index 14d543439..12841eee7 100644 --- a/backend/src/services/smtp.ts +++ b/backend/src/services/smtp.ts @@ -1,13 +1,14 @@ import nodemailer from 'nodemailer'; import { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SECURE } from '../config'; +import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN } from '../variables'; import SMTPConnection from 'nodemailer/lib/smtp-connection'; import * as Sentry from '@sentry/node'; const mailOpts: SMTPConnection.Options = { host: SMTP_HOST, - secure: SMTP_SECURE as boolean, port: SMTP_PORT as number }; + if (SMTP_USERNAME && SMTP_PASSWORD) { mailOpts.auth = { user: SMTP_USERNAME, @@ -15,6 +16,23 @@ if (SMTP_USERNAME && SMTP_PASSWORD) { }; } +if (SMTP_SECURE) { + switch (SMTP_HOST) { + case SMTP_HOST_SENDGRID: + mailOpts.requireTLS = true; + break; + case SMTP_HOST_MAILGUN: + mailOpts.requireTLS = true; + mailOpts.tls = { + ciphers: 'TLSv1.2' + } + break; + default: + mailOpts.secure = true; + break; + } +} + export const initSmtp = () => { const transporter = nodemailer.createTransport(mailOpts); transporter diff --git a/backend/src/types/express/index.d.ts b/backend/src/types/express/index.d.ts index 9a9e81449..319562fd0 100644 --- a/backend/src/types/express/index.d.ts +++ b/backend/src/types/express/index.d.ts @@ -1,5 +1,6 @@ import * as express from 'express'; + // TODO: fix (any) types declare global { namespace Express { diff --git a/backend/src/utils/errors.ts b/backend/src/utils/errors.ts new file mode 100644 index 000000000..40c467131 --- /dev/null +++ b/backend/src/utils/errors.ts @@ -0,0 +1,116 @@ +import RequestError, { LogLevel, RequestErrorContext } from "./requestError" + +//* ----->[GENERAL HTTP ERRORS]<----- +export const RouteNotFoundError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.INFO, + statusCode: error?.statusCode ?? 404, + type: error?.type ?? 'route_not_found', + message: error?.message ?? 'The requested source was not found', + context: error?.context, + stack: error?.stack +}) + +export const MethodNotAllowedError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.INFO, + statusCode: error?.statusCode ?? 405, + type: error?.type ?? 'method_not_allowed', + message: error?.message ?? 'The requested method is not allowed for the resource', + context: error?.context, + stack: error?.stack +}) + +export const UnauthorizedRequestError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.INFO, + statusCode: error?.statusCode ?? 401, + type: error?.type ?? 'unauthorized', + message: error?.message ?? 'You are not authorized to access this resource', + context: error?.context, + stack: error?.stack +}) + +export const ForbiddenRequestError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.INFO, + statusCode: error?.statusCode ?? 403, + type: error?.type ?? 'forbidden', + message: error?.message ?? 'You are not allowed to access this resource', + context: error?.context, + stack: error?.stack +}) + +export const BadRequestError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.INFO, + statusCode: error?.statusCode ?? 400, + type: error?.type ?? 'bad_request', + message: error?.message ?? 'The request is invalid or cannot be served', + context: error?.context, + stack: error?.stack +}) + +export const InternalServerError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.ERROR, + statusCode: error?.statusCode ?? 500, + type: error?.type ?? 'internal_server_error', + message: error?.message ?? 'The server encountered an error while processing the request', + context: error?.context, + stack: error?.stack +}) + +export const ServiceUnavailableError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.ERROR, + statusCode: error?.statusCode ?? 503, + type: error?.type ?? 'service_unavailable', + message: error?.message ?? 'The service is currently unavailable. Please try again later.', + context: error?.context, + stack: error?.stack +}) + +export const ValidationError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.ERROR, + statusCode: error?.statusCode ?? 400, + type: error?.type ?? 'validation_error', + message: error?.message ?? 'The request failed validation', + context: error?.context, + stack: error?.stack +}) + +//* ----->[INTEGRATION ERRORS]<----- +export const IntegrationNotFoundError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.ERROR, + statusCode: error?.statusCode ?? 404, + type: error?.type ?? 'integration_not_found_error', + message: error?.message ?? 'The requested integration was not found', + context: error?.context, + stack: error?.stack +}) + +//* ----->[WORKSPACE ERRORS]<----- +export const WorkspaceNotFoundError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.ERROR, + statusCode: error?.statusCode ?? 404, + type: error?.type ?? 'workspace_not_found_error', + message: error?.message ?? 'The requested workspace was not found', + context: error?.context, + stack: error?.stack +}) + +//* ----->[ORGANIZATION ERRORS]<----- +export const OrganizationNotFoundError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.ERROR, + statusCode: error?.statusCode ?? 404, + type: error?.type ?? 'organization_not_found_error', + message: error?.message ?? 'The requested organization was not found', + context: error?.context, + stack: error?.stack +}) + +//* ----->[ACCOUNT ERRORS]<----- +export const AccountNotFoundError = (error?: Partial) => new RequestError({ + logLevel: error?.logLevel ?? LogLevel.ERROR, + statusCode: error?.statusCode ?? 404, + type: error?.type ?? 'account_not_found_error', + message: error?.message ?? 'The requested account was not found', + context: error?.context, + stack: error?.stack +}) + +//* ----->[MISC ERRORS]<----- diff --git a/backend/src/utils/logger.ts b/backend/src/utils/logger.ts new file mode 100644 index 000000000..64c65ea49 --- /dev/null +++ b/backend/src/utils/logger.ts @@ -0,0 +1,65 @@ +/* eslint-disable no-console */ +import { createLogger, format, transports } from 'winston'; +import LokiTransport from 'winston-loki'; +import { LOKI_HOST, NODE_ENV } from '../config'; + +const { combine, colorize, label, printf, splat, timestamp } = format; + +const logFormat = (prefix: string) => combine( + timestamp(), + splat(), + label({ label: prefix }), + printf((info) => `${info.timestamp} ${info.label} ${info.level}: ${info.message}`) +); + +const createLoggerWithLabel = (level: string, label: string) => { + const _level = level.toLowerCase() || 'info' + //* Always add Console output to transports + const _transports: any[] = [ + new transports.Console({ + format: combine( + colorize(), + logFormat(label), + // format.json() + ) + }) + ] + //* Add LokiTransport if it's enabled + if(LOKI_HOST !== undefined){ + _transports.push( + new LokiTransport({ + host: LOKI_HOST, + handleExceptions: true, + handleRejections: true, + batching: true, + level: _level, + timeout: 30000, + format: format.combine( + format.json() + ), + labels: {app: process.env.npm_package_name, version: process.env.npm_package_version, environment: NODE_ENV}, + onConnectionError: (err: Error)=> console.error('Connection error while connecting to Loki Server.\n', err) + }) + ) + } + + + return createLogger({ + level: _level, + transports: _transports, + format: format.combine( + logFormat(label), + format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] }) + ) + }); +} + +const DEFAULT_LOGGERS = { + "backend-main": createLoggerWithLabel('info', '[IFSC:backend-main]'), + "database": createLoggerWithLabel('info', '[IFSC:database]'), +} +type LoggerNames = keyof typeof DEFAULT_LOGGERS + +export const getLogger = (loggerName: LoggerNames) => { + return DEFAULT_LOGGERS[loggerName] +} diff --git a/backend/src/utils/patchAsyncRoutes.js b/backend/src/utils/patchAsyncRoutes.js new file mode 100644 index 000000000..6f6d2367f --- /dev/null +++ b/backend/src/utils/patchAsyncRoutes.js @@ -0,0 +1,65 @@ +/* +Original work Copyright (c) 2016, Nikolay Nemshilov +Modified work Copyright (c) 2016, David Banham + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +*/ + +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-env node */ +const Layer = require('express/lib/router/layer'); +const Router = require('express/lib/router'); + +const last = (arr = []) => arr[arr.length - 1]; +const noop = Function.prototype; + +function copyFnProps(oldFn, newFn) { + Object.keys(oldFn).forEach((key) => { + newFn[key] = oldFn[key]; + }); + return newFn; +} + +function wrap(fn) { + const newFn = function newFn(...args) { + const ret = fn.apply(this, args); + const next = (args.length === 5 ? args[2] : last(args)) || noop; + if (ret && ret.catch) ret.catch(err => next(err)); + return ret; + }; + Object.defineProperty(newFn, 'length', { + value: fn.length, + writable: false, + }); + return copyFnProps(fn, newFn); +} + +export function patchRouterParam() { + const originalParam = Router.prototype.constructor.param; + Router.prototype.constructor.param = function param(name, fn) { + fn = wrap(fn); + return originalParam.call(this, name, fn); + }; +} + +Object.defineProperty(Layer.prototype, 'handle', { + enumerable: true, + get() { + return this.__handle; + }, + set(fn) { + fn = wrap(fn); + this.__handle = fn; + }, +}); \ No newline at end of file diff --git a/backend/src/utils/requestError.ts b/backend/src/utils/requestError.ts new file mode 100644 index 000000000..da2803da7 --- /dev/null +++ b/backend/src/utils/requestError.ts @@ -0,0 +1,113 @@ +import { Request } from 'express' +import { VERBOSE_ERROR_OUTPUT } from '../config' + +export enum LogLevel { + DEBUG = 100, + INFO = 200, + NOTICE = 250, + WARNING = 300, + ERROR = 400, + CRITICAL = 500, + ALERT = 550, + EMERGENCY = 600, +} + +export type RequestErrorContext = { + logLevel?: LogLevel, + statusCode: number, + type: string, + message: string, + context?: Record, + stack?: string|undefined +} + +export default class RequestError extends Error{ + + private _logLevel: LogLevel + private _logName: string + statusCode: number + type: string + context: Record + extra: Record[] + private stacktrace: string|undefined|string[] + + constructor( + {logLevel, statusCode, type, message, context, stack} : RequestErrorContext + ){ + super(message) + this._logLevel = logLevel || LogLevel.INFO + this._logName = LogLevel[this._logLevel] + this.statusCode = statusCode + this.type = type + this.context = context || {} + this.extra = [] + + if(stack) this.stack = stack + else Error.captureStackTrace(this, this.constructor) + this.stacktrace = this.stack?.split('\n') + } + + static convertFrom(error: Error) { + //This error was not handled by error handler. Please report this incident to the staff. + return new RequestError({ + logLevel: LogLevel.ERROR, + statusCode: 500, + type: 'internal_server_error', + message: 'This error was not handled by error handler. Please report this incident to the staff', + context: { + message: error.message, + name: error.name + }, + stack: error.stack + }) + } + + get level(){ return this._logLevel } + get levelName(){ return this._logName } + + withTags(...tags: string[]|number[]){ + this.context['tags'] = Object.assign(tags, this.context['tags']) + return this + } + + withExtras(...extras: Record[]){ + this.extra = Object.assign(extras, this.extra) + return this + } + + private _omit(obj: any, keys: string[]): typeof obj{ + const exclude = new Set(keys) + obj = Object.fromEntries(Object.entries(obj).filter(e => !exclude.has(e[0]))) + return obj + } + + public format(req: Request){ + let _context = Object.assign({ + stacktrace: this.stacktrace + }, this.context) + + //* Omit sensitive information from context that can leak internal workings of this program if user is not developer + if(!VERBOSE_ERROR_OUTPUT){ + _context = this._omit(_context, [ + 'stacktrace', + 'exception', + ]) + } + + const formatObject = { + type: this.type, + message: this.message, + context: _context, + level: this.level, + level_name: this.levelName, + status_code: this.statusCode, + datetime_iso: new Date().toISOString(), + application: process.env.npm_package_name || 'unknown', + request_id: req.headers["Request-Id"], + extra: this.extra + } + + return formatObject + + } +} \ No newline at end of file diff --git a/backend/src/variables/action.ts b/backend/src/variables/action.ts deleted file mode 100644 index 1f913bbe9..000000000 --- a/backend/src/variables/action.ts +++ /dev/null @@ -1,5 +0,0 @@ -const ACTION_PUSH_TO_HEROKU = 'pushToHeroku'; - -export { - ACTION_PUSH_TO_HEROKU -} \ No newline at end of file diff --git a/backend/src/variables/index.ts b/backend/src/variables/index.ts index c69ed8176..e284d6d5c 100644 --- a/backend/src/variables/index.ts +++ b/backend/src/variables/index.ts @@ -32,9 +32,9 @@ import { GRANTED } from './organization'; import { SECRET_SHARED, SECRET_PERSONAL } from './secret'; -import { PLAN_STARTER, PLAN_PRO } from './stripe'; import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from './event'; -import { ACTION_PUSH_TO_HEROKU } from './action'; +import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN } from './smtp'; +import { PLAN_STARTER, PLAN_PRO } from './stripe'; export { OWNER, @@ -44,8 +44,6 @@ export { ACCEPTED, COMPLETED, GRANTED, - PLAN_STARTER, - PLAN_PRO, SECRET_SHARED, SECRET_PERSONAL, ENV_DEV, @@ -69,6 +67,9 @@ export { INTEGRATION_GITHUB_API_URL, EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS, - ACTION_PUSH_TO_HEROKU, - INTEGRATION_OPTIONS + INTEGRATION_OPTIONS, + SMTP_HOST_SENDGRID, + SMTP_HOST_MAILGUN, + PLAN_STARTER, + PLAN_PRO, }; diff --git a/backend/src/variables/smtp.ts b/backend/src/variables/smtp.ts new file mode 100644 index 000000000..4db7c9f12 --- /dev/null +++ b/backend/src/variables/smtp.ts @@ -0,0 +1,7 @@ +const SMTP_HOST_SENDGRID = 'smtp.sendgrid.net'; +const SMTP_HOST_MAILGUN = 'smtp.mailgun.org'; + +export { + SMTP_HOST_SENDGRID, + SMTP_HOST_MAILGUN +} \ No newline at end of file diff --git a/cli/go.mod b/cli/go.mod index 1c175e2f0..86cc1763f 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -3,24 +3,37 @@ module github.com/Infisical/infisical-merge go 1.19 require ( + github.com/99designs/keyring v1.2.2 github.com/spf13/cobra v1.6.1 golang.org/x/crypto v0.3.0 + golang.org/x/term v0.3.0 ) require ( - github.com/alessio/shellescape v1.4.1 // indirect + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect + github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/danieljoos/wincred v1.1.2 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/dvsekhvalnov/jose2go v1.5.0 // indirect + github.com/go-openapi/errors v0.20.2 // indirect + github.com/go-openapi/strfmt v0.21.3 // indirect + github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect + github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mitchellh/mapstructure v1.3.3 // indirect + github.com/mtibben/percent v0.2.1 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + go.mongodb.org/mongo-driver v1.10.0 // indirect golang.org/x/net v0.2.0 // indirect - golang.org/x/sys v0.2.0 // indirect + golang.org/x/sys v0.3.0 // indirect ) require ( github.com/go-resty/resty/v2 v2.7.0 github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/manifoldco/promptui v0.9.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/pflag v1.0.5 // indirect - github.com/zalando/go-keyring v0.2.1 ) diff --git a/cli/go.sum b/cli/go.sum index 975332397..3419b8051 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,5 +1,9 @@ -github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= -github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= +github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= @@ -10,23 +14,57 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= +github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= +github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= +github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= +github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -34,32 +72,52 @@ github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc= -github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg= +go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cli/packages/cmd/root.go b/cli/packages/cmd/root.go index 7317b3eea..17f78b5d5 100644 --- a/cli/packages/cmd/root.go +++ b/cli/packages/cmd/root.go @@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{ Short: "Infisical CLI is used to inject environment variables into any process", Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`, CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true}, - Version: "0.1.11", + Version: "0.1.12", } // Execute adds all child commands to the root command and sets flags appropriately. @@ -31,4 +31,8 @@ func init() { rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging") rootCmd.PersistentFlags().StringVar(&util.INFISICAL_URL, "domain", "https://app.infisical.com/api", "Point the CLI to your own backend") + + rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { + util.InitKeyRingInstance() + } } diff --git a/cli/packages/cmd/vault.go b/cli/packages/cmd/vault.go new file mode 100644 index 000000000..8bb677682 --- /dev/null +++ b/cli/packages/cmd/vault.go @@ -0,0 +1,98 @@ +/* +Copyright © 2022 NAME HERE +*/ +package cmd + +import ( + "github.com/99designs/keyring" + "github.com/Infisical/infisical-merge/packages/util" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var vaultSetCmd = &cobra.Command{ + Example: `infisical vault set pass`, + Use: "set [vault-name]", + Short: "Used to set the vault backend to store your login details securely at rest", + DisableFlagsInUseLine: true, + PreRun: toggleDebug, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + wantedVaultTypeName := args[0] + currentVaultBackend, err := util.GetCurrentVaultBackend() + if err != nil { + log.Errorf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err) + return + } + + if wantedVaultTypeName == string(currentVaultBackend) { + log.Errorf("You are already on vault backend [%s]", currentVaultBackend) + return + } + + if isVaultToSwitchToValid(wantedVaultTypeName) { + configFile, err := util.GetConfigFile() + if err != nil { + log.Errorf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err) + return + } + + configFile.VaultBackendType = keyring.BackendType(wantedVaultTypeName) // save selected vault + configFile.LoggedInUserEmail = "" // reset the logged in user to prompt them to re login + + err = util.WriteConfigFile(&configFile) + if err != nil { + log.Errorf("Unable to set vault to [%s] because an error occurred when saving the config file [err=%s]") + return + } + + log.Infof("Successfully, switched vault backend from [%s] to [%s]. Please login in again to store your login details in the new vault with [infisical login]", currentVaultBackend, wantedVaultTypeName) + } else { + log.Errorf("The requested vault type [%s] is not available on this system. Only the following vault backends are available for you system: %s", wantedVaultTypeName, keyring.AvailableBackends()) + } + }, +} + +// runCmd represents the run command +var vaultCmd = &cobra.Command{ + Use: "vault", + Short: "Used to manage where your Infisical login token is saved on your machine", + DisableFlagsInUseLine: true, + PreRun: toggleDebug, + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + printAvailableVaultBackends() + }, +} + +func printAvailableVaultBackends() { + log.Infof("The following vaults are available on your system:") + for _, backend := range keyring.AvailableBackends() { + log.Infof("- %s", backend) + } + + currentVaultBackend, err := util.GetCurrentVaultBackend() + if err != nil { + log.Errorf("printAvailableVaultBackends: unable to print the available vault backend because of error [err=%s]", err) + } + + log.Infof("\nYou are currently using [%s] vault to store your login credentials", string(currentVaultBackend)) +} + +// Checks if the vault that the user wants to switch to is a valid available vault +func isVaultToSwitchToValid(vaultNameToSwitchTo string) bool { + isFound := false + for _, backend := range keyring.AvailableBackends() { + if vaultNameToSwitchTo == string(backend) { + isFound = true + break + } + } + + return isFound +} + +func init() { + vaultCmd.AddCommand(vaultSetCmd) + rootCmd.AddCommand(vaultCmd) +} diff --git a/cli/packages/models/cli.go b/cli/packages/models/cli.go index 708a9ed8e..0d1f96a05 100644 --- a/cli/packages/models/cli.go +++ b/cli/packages/models/cli.go @@ -1,5 +1,7 @@ package models +import "github.com/99designs/keyring" + type UserCredentials struct { Email string `json:"email"` PrivateKey string `json:"privateKey"` @@ -8,7 +10,8 @@ type UserCredentials struct { // The file struct for Infisical config file type ConfigFile struct { - LoggedInUserEmail string `json:"loggedInUserEmail"` + LoggedInUserEmail string `json:"loggedInUserEmail"` + VaultBackendType keyring.BackendType `json:"vaultBackendType"` } type SingleEnvironmentVariable struct { diff --git a/cli/packages/util/config.go b/cli/packages/util/config.go index 0ee04b013..02e37c0a1 100644 --- a/cli/packages/util/config.go +++ b/cli/packages/util/config.go @@ -24,8 +24,15 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error { } } + // get existing config + existingConfigFile, err := GetConfigFile() + if err != nil { + return fmt.Errorf("writeInitalConfig: unable to write config file because [err=%s]", err) + } + configFile := models.ConfigFile{ LoggedInUserEmail: userCredentials.Email, + VaultBackendType: existingConfigFile.VaultBackendType, } configFileMarshalled, err := json.Marshal(configFile) @@ -152,3 +159,50 @@ func GetAllWorkSpaceConfigsStartingFromCurrentPath() (workspaces []models.Worksp return listOfWorkSpaceConfigs, nil } + +// Get the infisical config file and if it doesn't exist, return empty config model, otherwise raise error +func GetConfigFile() (models.ConfigFile, error) { + fullConfigFilePath, _, err := GetFullConfigFilePath() + if err != nil { + return models.ConfigFile{}, err + } + + configFileAsBytes, err := os.ReadFile(fullConfigFilePath) + if err != nil { + if err, ok := err.(*os.PathError); ok { + return models.ConfigFile{}, nil + } else { + return models.ConfigFile{}, err + } + } + + var configFile models.ConfigFile + err = json.Unmarshal(configFileAsBytes, &configFile) + if err != nil { + return models.ConfigFile{}, err + } + + return configFile, nil +} + +// Write a ConfigFile to disk. Raise error if unable to save the model to ask +func WriteConfigFile(configFile *models.ConfigFile) error { + fullConfigFilePath, _, err := GetFullConfigFilePath() + if err != nil { + return fmt.Errorf("writeConfigFile: unable to write config file because an error occurred when getting config file path [err=%s]", err) + } + + configFileMarshalled, err := json.Marshal(configFile) + if err != nil { + return fmt.Errorf("writeConfigFile: unable to write config file because an error occurred when marshalling the config file [err=%s]", err) + } + + // Create file in directory + err = WriteToFile(fullConfigFilePath, configFileMarshalled, os.ModePerm) + if err != nil { + return fmt.Errorf("writeConfigFile: unable to write config file because an error occurred when write the config to file [err=%s]", err) + + } + + return nil +} diff --git a/cli/packages/util/credentials.go b/cli/packages/util/credentials.go index c5396f698..b83227b71 100644 --- a/cli/packages/util/credentials.go +++ b/cli/packages/util/credentials.go @@ -3,12 +3,11 @@ package util import ( "encoding/json" "fmt" - "os" + "github.com/99designs/keyring" "github.com/Infisical/infisical-merge/packages/models" "github.com/go-resty/resty/v2" log "github.com/sirupsen/logrus" - "github.com/zalando/go-keyring" ) const SERVICE_NAME = "infisical" @@ -17,26 +16,30 @@ const SERVICE_NAME = "infisical" func StoreUserCredsInKeyRing(userCred *models.UserCredentials) error { userCredMarshalled, err := json.Marshal(userCred) if err != nil { - return fmt.Errorf("Something went wrong when marshalling user creds:", err) + return fmt.Errorf("StoreUserCredsInKeyRing: something went wrong when marshalling user creds [err=%s]", err) } - err = keyring.Set(SERVICE_NAME, userCred.Email, string(userCredMarshalled)) + err = keyringInstance.Set(keyring.Item{ + Key: userCred.Email, + Data: []byte(string(userCredMarshalled)), + }) + if err != nil { - return fmt.Errorf("Unable to store user credentials:", err) + return fmt.Errorf("StoreUserCredsInKeyRing: unable to store user credentials because [err=%s]", err) } return err } func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentials, err error) { - credentialsString, err := keyring.Get(SERVICE_NAME, userEmail) + credentialsValue, err := keyringInstance.Get(userEmail) if err != nil { - return models.UserCredentials{}, fmt.Errorf("Unable to get key from Keyring:", err) + return models.UserCredentials{}, fmt.Errorf("Unable to get key from Keyring. could not find login credentials in your Keyring. This is common if you have switched vault backend recently. If so, please login in again and retry:", err) } var userCredentials models.UserCredentials - err = json.Unmarshal([]byte(credentialsString), &userCredentials) + err = json.Unmarshal([]byte(credentialsValue.Data), &userCredentials) if err != nil { return models.UserCredentials{}, fmt.Errorf("Something went wrong when unmarshalling user creds:", err) } @@ -50,23 +53,13 @@ func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentia func IsUserLoggedIn() (hasUserLoggedIn bool, theUsersEmail string, err error) { if ConfigFileExists() { - fullConfigFilePath, _, err := GetFullConfigFilePath() + configFile, err := GetConfigFile() if err != nil { - log.Debugln("Error gettting full path:", err) - return false, "", err + return false, "", fmt.Errorf("IsUserLoggedIn: unable to get logged in user from config file [err=%s]", err) } - configFileAsBytes, err := os.ReadFile(fullConfigFilePath) - if err != nil { - log.Debugln("Unable to read config file:", err) - return false, "", err - } - - var configFile models.ConfigFile - err = json.Unmarshal(configFileAsBytes, &configFile) - if err != nil { - log.Debugln("Unable to unmarshal config file:", err) - return false, "", err + if configFile.LoggedInUserEmail == "" { + return false, "", nil } userCreds, err := GetUserCredsFromKeyRing(configFile.LoggedInUserEmail) diff --git a/cli/packages/util/vault.go b/cli/packages/util/vault.go new file mode 100644 index 000000000..95dba04d8 --- /dev/null +++ b/cli/packages/util/vault.go @@ -0,0 +1,71 @@ +package util + +import ( + "fmt" + "os" + + "github.com/99designs/keyring" + log "github.com/sirupsen/logrus" + "golang.org/x/term" +) + +// Keyring instance +var keyringInstance keyring.Keyring +var keyringInstanceConfig keyring.Config + +func GetCurrentVaultBackend() (keyring.BackendType, error) { + configFile, err := GetConfigFile() + if err != nil { + return "", fmt.Errorf("getCurrentVaultBackend: unable to get config file [err=%s]", err) + } + + if configFile.VaultBackendType == "" { + if keyring.AvailableBackends()[0] == keyring.FileBackend { + } + return keyring.AvailableBackends()[0], nil + } + + return configFile.VaultBackendType, nil +} + +func InitKeyRingInstance() { + currentVaultBackend, err := GetCurrentVaultBackend() + if err != nil { + log.Infof("InitKeyRingInstance: unable to get the current vault backend, [err=%s]", err) + } + + keyringInstanceConfig = keyring.Config{ + FilePasswordFunc: fileKeyringPassphrasePrompt, + ServiceName: SERVICE_NAME, + LibSecretCollectionName: SERVICE_NAME, + KWalletAppID: SERVICE_NAME, + KWalletFolder: SERVICE_NAME, + KeychainTrustApplication: true, + WinCredPrefix: SERVICE_NAME, + FileDir: fmt.Sprintf("~/%s-file-vault", SERVICE_NAME), + KeychainAccessibleWhenUnlocked: true, + } + + // if the user explicitly sets a vault backend, then only use that + if currentVaultBackend != "" { + keyringInstanceConfig.AllowedBackends = []keyring.BackendType{keyring.BackendType(currentVaultBackend)} + } + + keyringInstance, err = keyring.Open(keyringInstanceConfig) + if err != nil { + log.Errorf("InitKeyRingInstance: Unable to create instance of Keyring because of [err=%s]", err) + } +} + +func fileKeyringPassphrasePrompt(prompt string) (string, error) { + if password, ok := os.LookupEnv("INFISICAL_VAULT_FILE_PASSPHRASE"); ok { + return password, nil + } + + fmt.Fprintf(os.Stderr, "%s: ", prompt) + b, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/cli/packages/visualize/visualize.go b/cli/packages/visualize/visualize.go new file mode 100644 index 000000000..85d946a32 --- /dev/null +++ b/cli/packages/visualize/visualize.go @@ -0,0 +1,35 @@ +package visualize + +import ( + "os" + + "github.com/jedib0t/go-pretty/table" +) + +// Given headers and rows, this function will print out a table +func Table(headers []string, rows [][]string) { + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.SetStyle(table.StyleLight) + + // t.SetTitle("Title") + t.Style().Options.DrawBorder = true + t.Style().Options.SeparateHeader = true + t.Style().Options.SeparateColumns = true + + tableHeaders := table.Row{} + for _, header := range headers { + tableHeaders = append(tableHeaders, header) + } + + t.AppendHeader(tableHeaders) + for _, row := range rows { + tableRow := table.Row{} + for _, val := range row { + tableRow = append(tableRow, val) + } + t.AppendRow(tableRow) + } + + t.Render() +} diff --git a/docs/cli/commands/commands.mdx b/docs/cli/commands/commands.mdx index acefdfa7e..7c1deeb1b 100644 --- a/docs/cli/commands/commands.mdx +++ b/docs/cli/commands/commands.mdx @@ -9,7 +9,7 @@ title: "Commands" | `login` | Used to authenticate and set the logged in user. | | `init` | Used to link a local project to the platform. | | `run` | Used to inject envars from the platform into an application process. | - +| `vault` | Used to manage where your login credentials are stored at rest | ## Global options | Option | Description | diff --git a/docs/cli/commands/login.mdx b/docs/cli/commands/login.mdx index 32c1bedba..de004c97f 100644 --- a/docs/cli/commands/login.mdx +++ b/docs/cli/commands/login.mdx @@ -7,7 +7,5 @@ infisical login ``` ## Description - -Verify a user and save credentials to the system keyring. - -To change the logged in user, run the command again to overwrite the previous login. +The CLI uses authentication to verify your identity. When you enter the correct email and password for your account, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI. +If you want to change where the login credentials are stored, visit the [vaults command](./vault) \ No newline at end of file diff --git a/docs/cli/commands/vault.mdx b/docs/cli/commands/vault.mdx new file mode 100644 index 000000000..7ffec4e91 --- /dev/null +++ b/docs/cli/commands/vault.mdx @@ -0,0 +1,52 @@ +--- +title: "infisical vault" +--- + + + + ```bash + infisical vault + + # Example output + The following vaults are available on your system: + - keychain + - pass + - file + + You are currently using [keychain] vault to store your login credentials + ``` + + + + ```bash + infisical vault set + + # Example + infisical vault set keychain + ``` + + + + +## Description + +To ensure secure storage of your login credentials when using the CLI, Infisical saves them to a password manager if one is detected. +If a password manager is not available, your credentials are stored in an encrypted text file. + + + + By default, the most appropriate password manager is chosen to store your login credentials. + For example, if you are on macOS, KeyChain will be automatically selected. + +- [macOS Keychain](https://support.apple.com/en-au/guide/keychain-access/welcome/mac) +- [Windows Credential Manager](https://support.microsoft.com/en-au/help/4026814/windows-accessing-credential-manager) +- Secret Service ([Gnome Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5)) +- [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5) +- [Pass](https://www.passwordstore.org/) +- [KeyCtl]() +- Encrypted file (JWT) + + +To avoid constantly entering your passphrase when using the `file` vault type, set the `INFISICAL_VAULT_FILE_PASSPHRASE` environment variable with your password in your shell + + diff --git a/docs/cli/usage.mdx b/docs/cli/usage.mdx index 4237f8154..aac5c1b74 100644 --- a/docs/cli/usage.mdx +++ b/docs/cli/usage.mdx @@ -4,10 +4,27 @@ title: "Usage" Prerequisite: [Install the CLI](/cli/overview) +## Authenticate + + + To use the Infisical CLI in your development environment, you can run the command below. + This will allow you to access the features and functionality provided by the CLI. + + ```bash + infisical login + ``` + + + + To use Infisical CLI in environments where you cannot run the `infisical login` command, you can authenticate via a + Infisical Token instead. Learn more about [Infisical Token](../getting-started/dashboard/token). + + + ## Initialize Infisical for your project ```bash -# move to your project +# navigate to your project cd /path/to/project # initialize infisical diff --git a/docs/contributing/FAQ.mdx b/docs/contributing/FAQ.mdx deleted file mode 100644 index 9f4f7aee9..000000000 --- a/docs/contributing/FAQ.mdx +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: 'Frequently Asked Questions' -description: 'Have any questions? [Join our Slack community](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g).' ---- - -## Problem with SMTP - -If you opt for actual SMTP server (not the local MailHog), you have to have the right environment variables set. - -You can normally populate `SMTP_USERNAME` and `SMTP_PASSWORD` with your usual login and password (you could also create a 'burner' email). Sometimes, there still are problems. - -You can go to your Gmail account settings > security and enable “less secure apps”. This would allow Infisical to use your Gmail to send emails. - -If it still doesn't work, [this](https://stackoverflow.com/questions/72547853/unable-to-send-email-in-c-sharp-less-secure-app-access-not-longer-available/72553362#72553362) should help. - -## `MONGO_URL` issues - -Your `MONGO_URL` should be something like `mongodb://root:example@mongo:27017/?authSource=admin`. If you want to change it (not recommended), you should make sure that you keep this URL in line with `MONGO_USERNAME=root` and `MONGO_PASSWORD=example`. diff --git a/docs/contributing/developing.mdx b/docs/contributing/developing.mdx index 812025dbc..423b83da2 100644 --- a/docs/contributing/developing.mdx +++ b/docs/contributing/developing.mdx @@ -16,53 +16,43 @@ cd infisical ## Set up environment variables -Start by creating a .env file at the root of the Infisical directory +Start by creating a .env file at the root of the Infisical directory. It's best to start with the provided [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) template containing the necessary envars to fill out your .env file — you only have to modify the SMTP parameters. + + + The pre-populated environment variable values in the `.env.example` file are meant to be used in development only. + You'll want to fill in your own values in production, especially concerning encryption keys, secrets, and SMTP parameters. + + +Refer to the [environment variable list](https://infisical.com/docs/self-hosting/configuration/envars) for guidance on each envar. + +### Helpful tips for developing with Infisical: - Reference the [environment variable list](https://infisical.com/docs/self-hosting/configuration/envars) and provided [`.env.example`](https://raw.githubusercontent.com/Infisical/infisical/main/.env.example) template to fill out your .env file. +Use the `ENCRYPTION_KEY`, JWT-secret envars, `MONGO_URL`, `MONGO_USERNAME`, `MONGO_PASSWORD` provided in the `.env.example` file. + +If setting your own values: + +- `ENCRYPTION_KEY` should be a [32-byte random hex](https://www.browserling.com/tools/random-hex) +- `MONGO_URL` should take the form: `mongodb://[MONGO_USERNAME]:[MONGO_PASSWORD]@mongo:27017/?authSource=admin`. -### Keys + +Bring and configure your own SMTP server by following our [email configuration guide](https://infisical.com/docs/self-hosting/configuration/email) (we recommend using either SendGrid or Mailgun). -`ENCRYPTION_KEY`, `JWT_SIGNUP_SECRET`, `JWT_REFRESH_SECRET`, `JWT_AUTH_SECRET`, `JWT_SERVICE_SECRET` values can be generated with this [32-byte random hex generator](https://www.browserling.com/tools/random-hex). - -### Database - -Use to the following `MONGO_URL`, `MONGO_USERNAME`, `MONGO_PASSWORD`, `SITE_URL` values: - -``` -MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin -MONGO_USERNAME=root -MONGO_PASSWORD=example - -SITE_URL=http://localhost:8080 -``` - - - If you decide to use your own `MONGO_USERNAME` and `MONGO_PASSWORD`, you'll have to modify `MONGO_URL` to take the form: `mongodb://[MONGO_USERNAME]:[MONGO_PASSWORD]@mongo:27017/?authSource=admin`. - - -### Mailing - -Option 1: Bring your own SMTP server and credentials by filling in `SMTP_HOST`, `SMTP_FROM_ADDRESS`, `SMTP_FROM_NAME`, `SMTP_USERNAME`, and `SMTP_PASSWORD`. - - `SMTP_HOST` is set to `smtp.gmail.com` by default. For `SMTP_USERNAME` and `SMTP_PASSWORD`, you'll need an email with 2-step-verification and an [app password](https://support.google.com/mail/answer/185833?hl=en) for it. - - - -Option 2: Use the provided (Mailhog) SMTP server and browse emails sent by the backend on `http://localhost:8025`. To use this option, set the following `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM_NAME`, `SMTP_USERNAME`, `SMTP_PASSWORD` values: +Alternatively, you can use the provided development (Mailhog) SMTP server to send and browse emails sent by the backend on http://localhost:8025; to use this option, set the following `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM_NAME`, `SMTP_USERNAME`, `SMTP_PASSWORD` below. + ``` SMTP_HOST=smtp-server SMTP_PORT=1025 SMTP_FROM_ADDRESS=team@infisical.com -SMTP_FROM_NAME=[whatever you like] +SMTP_FROM_NAME=Infisical SMTP_USERNAME=team@infisical.com SMTP_PASSWORD= ``` - Make sure to leave the `SMTP_PASSWORD` blank so the backend can connect to MailHog. + If using Mailhog, make sure to leave the `SMTP_PASSWORD` blank so the backend can connect to MailHog. ## Docker for development diff --git a/docs/getting-started/dashboard/token.mdx b/docs/getting-started/dashboard/token.mdx index 9b3ddf79f..455a57929 100644 --- a/docs/getting-started/dashboard/token.mdx +++ b/docs/getting-started/dashboard/token.mdx @@ -11,6 +11,12 @@ To generate the the token, head over to your project settings as shown below. ![token add](../../images/project-token-add.png) +## Feeding Infisical Token to the CLI + +The Infisical CLI checks for the presence of an environment variable called `INFISICAL_TOKEN`. +If it detects this variable in the terminal where it is being run, it will use it to authenticate and retrieve the environment variables that the token is authorized to access. +This allows you to use the CLI in environments where you are unable to run the `infisical login` command. + The token grants read-only access to a particular environment and project for a specified amount of time. Once the token is expired, the CLI using it will no longer be able to make diff --git a/docs/images/email-mailhog-credentials.png b/docs/images/email-mailhog-credentials.png new file mode 100644 index 000000000..8d5a11295 Binary files /dev/null and b/docs/images/email-mailhog-credentials.png differ diff --git a/docs/images/email-sendgrid-create-key.png b/docs/images/email-sendgrid-create-key.png new file mode 100644 index 000000000..1caa977a8 Binary files /dev/null and b/docs/images/email-sendgrid-create-key.png differ diff --git a/docs/images/email-sendgrid-restrictions.png b/docs/images/email-sendgrid-restrictions.png new file mode 100644 index 000000000..a70891a60 Binary files /dev/null and b/docs/images/email-sendgrid-restrictions.png differ diff --git a/docs/mint.json b/docs/mint.json index 92a952767..57beea626 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -94,7 +94,8 @@ "cli/commands/login", "cli/commands/init", "cli/commands/run", - "cli/commands/export" + "cli/commands/export", + "cli/commands/vault" ] } ] @@ -102,18 +103,21 @@ { "group": "Self-hosting", "pages": [ - "self-hosting/overview", - { - "group": "Deployments options", - "pages": [ - "self-hosting/deployments/linux", - "self-hosting/deployments/kubernetes" - ] - }, - { - "group": "Configuration", - "pages": ["self-hosting/configuration/envars"] - } + "self-hosting/overview" + ] + }, + { + "group": "Deployment options", + "pages": [ + "self-hosting/deployments/linux", + "self-hosting/deployments/kubernetes" + ] + }, + { + "group": "Configuration", + "pages": [ + "self-hosting/configuration/envars", + "self-hosting/configuration/email" ] }, { @@ -177,8 +181,7 @@ "pages": [ "contributing/overview", "contributing/code-of-conduct", - "contributing/developing", - "contributing/FAQ" + "contributing/developing" ] } ], diff --git a/docs/self-hosting/configuration/email.mdx b/docs/self-hosting/configuration/email.mdx new file mode 100644 index 000000000..b1e911fb6 --- /dev/null +++ b/docs/self-hosting/configuration/email.mdx @@ -0,0 +1,75 @@ +--- +title: "Email" +description: "" +--- + +Infisical requires you to configure your own SMTP server for certain functionality like: + +- Sending email confirmation links to sign up. +- Sending invite links for projects. +- Sending alerts. + +We strongly recommend using an email service to act as your email server and provide examples for common providers. + +## General configuration + +By default, you need to configure the following SMTP [environment variables](https://infisical.com/docs/self-hosting/configuration/envars): + +- `SMTP_HOST`: Hostname to connect to for establishing SMTP connections. +- `SMTP_USERNAME`: Credential to connect to host (e.g. team@infisical.com) +- `SMTP_PASSWORD`: Credential to connect to host. +- `SMTP_PORT`: Port to connect to for establishing SMTP connections. +- `SMTP_SECURE`: If `true`, the connection will use TLS when connecting to server with special configs for SendGrid and Mailgun. If `false` (the default) then TLS is used if server supports the STARTTLS extension. +- `SMTP_FROM_ADDRESS`: Email address to be used for sending emails (e.g. team@infisical.com). +- `SMTP_FROM_NAME`: Name label to be used in `From` field (e.g. Team). + +Below you will find details on how to configure common email providers (not in any particular order). + +## Twilio SendGrid + +1. Create an account and configure [SendGrid](https://sendgrid.com) to send emails. +2. Create a SendGrid API Key under Settings > [API Keys](https://app.sendgrid.com/settings/api_keys) +3. Set a name for your API Key, we recommend using "Infisical," and select the "Restricted Key" option. You will need to enable the "Mail Send" permission as shown below: + +![creating sendgrid api key](../../images/email-sendgrid-create-key.png) + +![setting sendgrid api key restriction](../../images/email-sendgrid-restrictions.png) + +4. With the API Key, you can now set your SMTP environment variables: + +``` +SMTP_HOST=smtp.sendgrid.net +SMTP_USERNAME=apikey +SMTP_PASSWORD=SG.rqFsfjxYPiqE1lqZTgD_lz7x8IVLx # your SendGrid API Key from step above +SMTP_PORT=587 +SMTP_SECURE=true +SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails +SMTP_FROM_NAME=Infisical +``` + + + Remember that you will need to restart Infisical for this to work properly. + + +## Mailgun + +1. Create an account and configure [Mailgun](https://www.mailgun.com) to send emails. +2. Obtain your Mailgun credentials in Sending > Overview > SMTP + +![obtain mailhog api key estriction](../../images/email-mailhog-credentials.png) + +3. With your Mailgun credentials, you can now set up your SMTP environment variables: + +``` +SMTP_HOST=smtp.mailgun.org # obtained from credentials page +SMTP_USERNAME=postmaster@example.mailgun.org # obtained from credentials page +SMTP_PASSWORD=password # obtained from credentials page +SMTP_PORT=587 +SMTP_SECURE=true +SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails +SMTP_FROM_NAME=Infisical +``` + + + Remember that you will need to restart Infisical for this to work properly. + \ No newline at end of file diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index 8bbb507d8..0b9fd5e71 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -3,9 +3,7 @@ title: "Environment Variables" description: "" --- -## The .env file - -Configuring Infisical requires setting some environment variables. There is a file called `.env.example` at the root directory of our main repo that you can use to create a `.env` before you start the server. +Configuring Infisical requires setting some environment variables. There is a file called [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) at the root directory of our main repo that you can use to create a `.env` file before you start the server. | Variable | Description | Default Value | | ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------- | @@ -22,13 +20,13 @@ Configuring Infisical requires setting some environment variables. There is a fi | `MONGO_USERNAME` | MongoDB username if using container | `None` | | `MONGO_PASSWORD` | MongoDB password if using container | `None` | | `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` | -| `SMTP_HOST` | Hostname to connect to for establishing SMTP connections | `smtp.gmail.com` | -| `SMTP_SECURE` | Use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` | -| `SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` | -| `SMTP_FROM_ADDRESS` | ❗️ Email address to be used for sending emails (e.g. `team@infisical.com`) | `None` | -| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` | +| `SMTP_HOST` | ❗️ Hostname to connect to for establishing SMTP connections | `None` | | `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` | | `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` | +| `SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` | +| `SMTP_SECURE` | If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` | +| `SMTP_FROM_ADDRESS` | ❗️ Email address to be used for sending emails (e.g. `team@infisical.com`) | `None` | +| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` | | `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` | | `CLIENT_ID_HEROKU` | OAuth2 client ID for Heroku integration | `None` | | `CLIENT_ID_VERCEL` | OAuth2 client ID for Vercel integration | `None` | diff --git a/frontend/pages/404.tsx b/frontend/pages/404.tsx index 19edc86c6..6192f8773 100644 --- a/frontend/pages/404.tsx +++ b/frontend/pages/404.tsx @@ -1,6 +1,7 @@ import React from "react"; import Head from "next/head"; import Image from "next/image"; +import Link from "next/link"; export default function Custom404() { return ( @@ -12,9 +13,9 @@ export default function Custom404() {

Oops, something went wrong

Think this is a mistake? Email team@infisical.com and we`ll fix it!

- + Go to Dashboard - + { + setIsResendingVerificationEmail(true); + setIsLoading(true); + await sendVerificationEmail(email); + setTimeout(() => { + setIsLoading(false); + setIsResendingVerificationEmail(false); + }, 2000); + }; + // Step 1 of the sign up process (enter the email or choose google authentication) const step1 = (
@@ -262,84 +273,86 @@ export default function SignUp() {
-
- -
+
+ +
{/*

I do not want to receive emails about Infisical and its products.

*/} -
-

- By creating an account, you agree to our Terms and have read and - acknowledged the Privacy Policy. -

-
-
+
+

+ By creating an account, you agree to our Terms and have read and + acknowledged the Privacy Policy. +

+
+
+
); // Step 2 of the signup process (enter the email verification code) const step2 = ( -
-

- {'We\'ve'} sent a verification email to{' '} +

+

+ {'We\'ve'} sent a verification email to{' '} +

+

+ {email}{' '} +

+
+ +
+
+ +
+ {codeError && ( + + )} +
+
+
+
+ + Not seeing an email? + + + + +
+

+ Make sure to check your spam inbox.

-

- {email}{' '} -

-
- -
-
- -
- {codeError && ( - - )} -
-
-
- {/* - - */} -

- Make sure to check your spam inbox. -

-
); @@ -498,24 +511,6 @@ export default function SignUp() { It contains your Secret Key which we cannot access or recover for you if you lose it.
-
-