Merge branch 'main' of github.com:LemmyMwaura/infisical into secrets-dash-jumping-bug-#51

This commit is contained in:
lemmy mwaura
2022-12-11 23:50:35 +03:00
163 changed files with 5441 additions and 2850 deletions
+1
View File
@@ -40,6 +40,7 @@ SITE_URL=http://localhost:8080
# Required to send emails # Required to send emails
# By default, SMTP_HOST is set to smtp.gmail.com # By default, SMTP_HOST is set to smtp.gmail.com
SMTP_HOST=smtp.gmail.com SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_NAME=Team SMTP_NAME=Team
SMTP_USERNAME=[email protected] SMTP_USERNAME=[email protected]
SMTP_PASSWORD= SMTP_PASSWORD=
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
+7
View File
@@ -0,0 +1,7 @@
{
"semi": true,
"trailingComma": "none",
"singleQuote": true,
"printWidth": 80,
"useTabs": false
}
+1 -1
View File
@@ -311,4 +311,4 @@ Looking to report a security vulnerability? Please don't post about it in GitHub
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
+2 -7
View File
@@ -1,10 +1,6 @@
{ {
"root": true,
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"plugins": [ "plugins": ["@typescript-eslint", "prettier"],
"@typescript-eslint",
"prettier"
],
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/eslint-recommended",
@@ -12,7 +8,6 @@
"prettier" "prettier"
], ],
"rules": { "rules": {
"no-console": 2, "no-console": 2
"prettier/prettier": 2
} }
} }
-7
View File
@@ -1,7 +0,0 @@
{
"semi": true,
"trailingComma": "none",
"singleQuote": true,
"printWidth": 80,
"useTabs": true
}
+15 -94
View File
@@ -28,7 +28,7 @@
"mongoose": "^6.7.2", "mongoose": "^6.7.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"posthog-node": "^2.1.0", "posthog-node": "^2.1.0",
"query-string": "^7.1.1", "query-string": "^7.1.3",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stripe": "^10.7.0", "stripe": "^10.7.0",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
@@ -50,7 +50,6 @@
"eslint": "^8.26.0", "eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"husky": "^8.0.1",
"install": "^0.13.0", "install": "^0.13.0",
"jest": "^29.3.1", "jest": "^29.3.1",
"nodemon": "^2.0.19", "nodemon": "^2.0.19",
@@ -2594,19 +2593,6 @@
"@maxmind/geoip2-node": "^3.4.0" "@maxmind/geoip2-node": "^3.4.0"
} }
}, },
"node_modules/@sentry/core": {
"version": "7.17.4",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.17.4.tgz",
"integrity": "sha512-U3ABSJBKGK8dJ01nEG2+qNOb6Wv7U3VqoajiZxfV4lpPWNFGCoEhiTytxBlFTOCmdUH8209zSZiWJZaDLy+TSA==",
"dependencies": {
"@sentry/types": "7.17.4",
"@sentry/utils": "7.17.4",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/node": { "node_modules/@sentry/node": {
"version": "7.19.0", "version": "7.19.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.19.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.19.0.tgz",
@@ -2704,26 +2690,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/types": {
"version": "7.17.4",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.17.4.tgz",
"integrity": "sha512-QJj8vO4AtxuzQfJIzDnECSmoxwnS+WJsm1Ta2Cwdy+TUCBJyWpW7aIJJGta76zb9gNPGb3UcAbeEjhMJBJeRMQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils": {
"version": "7.17.4",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.17.4.tgz",
"integrity": "sha512-ioG0ANy8uiWzig82/e7cc+6C9UOxkyBzJDi1luoQVDH6P0/PvM8GzVU+1iUVUipf8+OL1Jh09GrWnd5wLm3XNQ==",
"dependencies": {
"@sentry/types": "7.17.4",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sinclair/typebox": { "node_modules/@sinclair/typebox": {
"version": "0.24.51", "version": "0.24.51",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
@@ -4035,9 +4001,9 @@
} }
}, },
"node_modules/decode-uri-component": { "node_modules/decode-uri-component": {
"version": "0.2.0", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"
} }
@@ -5146,21 +5112,6 @@
"node": ">=10.17.0" "node": ">=10.17.0"
} }
}, },
"node_modules/husky": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz",
"integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==",
"dev": true,
"bin": {
"husky": "lib/bin.js"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -9703,11 +9654,11 @@
} }
}, },
"node_modules/query-string": { "node_modules/query-string": {
"version": "7.1.1", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"dependencies": { "dependencies": {
"decode-uri-component": "^0.2.0", "decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0", "filter-obj": "^1.1.0",
"split-on-first": "^1.0.0", "split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0" "strict-uri-encode": "^2.0.0"
@@ -13113,16 +13064,6 @@
"@maxmind/geoip2-node": "^3.4.0" "@maxmind/geoip2-node": "^3.4.0"
} }
}, },
"@sentry/core": {
"version": "7.17.4",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.17.4.tgz",
"integrity": "sha512-U3ABSJBKGK8dJ01nEG2+qNOb6Wv7U3VqoajiZxfV4lpPWNFGCoEhiTytxBlFTOCmdUH8209zSZiWJZaDLy+TSA==",
"requires": {
"@sentry/types": "7.17.4",
"@sentry/utils": "7.17.4",
"tslib": "^1.9.3"
}
},
"@sentry/node": { "@sentry/node": {
"version": "7.19.0", "version": "7.19.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.19.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.19.0.tgz",
@@ -13200,20 +13141,6 @@
} }
} }
}, },
"@sentry/types": {
"version": "7.17.4",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.17.4.tgz",
"integrity": "sha512-QJj8vO4AtxuzQfJIzDnECSmoxwnS+WJsm1Ta2Cwdy+TUCBJyWpW7aIJJGta76zb9gNPGb3UcAbeEjhMJBJeRMQ=="
},
"@sentry/utils": {
"version": "7.17.4",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.17.4.tgz",
"integrity": "sha512-ioG0ANy8uiWzig82/e7cc+6C9UOxkyBzJDi1luoQVDH6P0/PvM8GzVU+1iUVUipf8+OL1Jh09GrWnd5wLm3XNQ==",
"requires": {
"@sentry/types": "7.17.4",
"tslib": "^1.9.3"
}
},
"@sinclair/typebox": { "@sinclair/typebox": {
"version": "0.24.51", "version": "0.24.51",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
@@ -14219,9 +14146,9 @@
} }
}, },
"decode-uri-component": { "decode-uri-component": {
"version": "0.2.0", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==" "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
}, },
"dedent": { "dedent": {
"version": "0.7.0", "version": "0.7.0",
@@ -15034,12 +14961,6 @@
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true "dev": true
}, },
"husky": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz",
"integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==",
"dev": true
},
"iconv-lite": { "iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -18324,11 +18245,11 @@
} }
}, },
"query-string": { "query-string": {
"version": "7.1.1", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"requires": { "requires": {
"decode-uri-component": "^0.2.0", "decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0", "filter-obj": "^1.1.0",
"split-on-first": "^1.0.0", "split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0" "strict-uri-encode": "^2.0.0"
+3 -3
View File
@@ -19,7 +19,7 @@
"mongoose": "^6.7.2", "mongoose": "^6.7.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"posthog-node": "^2.1.0", "posthog-node": "^2.1.0",
"query-string": "^7.1.1", "query-string": "^7.1.3",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stripe": "^10.7.0", "stripe": "^10.7.0",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
@@ -35,7 +35,8 @@
"build": "rimraf ./build && tsc && cp -R ./src/templates ./src/json ./build", "build": "rimraf ./build && tsc && cp -R ./src/templates ./src/json ./build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint-and-fix": "eslint . --ext .ts --fix", "lint-and-fix": "eslint . --ext .ts --fix",
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write" "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
"lint-staged": "lint-staged"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -63,7 +64,6 @@
"eslint": "^8.26.0", "eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"husky": "^8.0.1",
"install": "^0.13.0", "install": "^0.13.0",
"jest": "^29.3.1", "jest": "^29.3.1",
"nodemon": "^2.0.19", "nodemon": "^2.0.19",
+6 -2
View File
@@ -13,12 +13,15 @@ const NODE_ENV = process.env.NODE_ENV! || 'production';
const OAUTH_CLIENT_SECRET_HEROKU = process.env.OAUTH_CLIENT_SECRET_HEROKU!; const OAUTH_CLIENT_SECRET_HEROKU = process.env.OAUTH_CLIENT_SECRET_HEROKU!;
const OAUTH_TOKEN_URL_HEROKU = process.env.OAUTH_TOKEN_URL_HEROKU!; const OAUTH_TOKEN_URL_HEROKU = process.env.OAUTH_TOKEN_URL_HEROKU!;
const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com'; const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com';
const POSTHOG_PROJECT_API_KEY = process.env.POSTHOG_PROJECT_API_KEY! || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; const POSTHOG_PROJECT_API_KEY =
process.env.POSTHOG_PROJECT_API_KEY! ||
'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
const PRIVATE_KEY = process.env.PRIVATE_KEY!; const PRIVATE_KEY = process.env.PRIVATE_KEY!;
const PUBLIC_KEY = process.env.PUBLIC_KEY!; const PUBLIC_KEY = process.env.PUBLIC_KEY!;
const SENTRY_DSN = process.env.SENTRY_DSN!; const SENTRY_DSN = process.env.SENTRY_DSN!;
const SITE_URL = process.env.SITE_URL!; const SITE_URL = process.env.SITE_URL!;
const SMTP_HOST = process.env.SMTP_HOST! || 'smtp.gmail.com'; const SMTP_HOST = process.env.SMTP_HOST! || 'smtp.gmail.com';
const SMTP_PORT = process.env.SMTP_PORT! || 587;
const SMTP_NAME = process.env.SMTP_NAME!; const SMTP_NAME = process.env.SMTP_NAME!;
const SMTP_USERNAME = process.env.SMTP_USERNAME!; const SMTP_USERNAME = process.env.SMTP_USERNAME!;
const SMTP_PASSWORD = process.env.SMTP_PASSWORD!; const SMTP_PASSWORD = process.env.SMTP_PASSWORD!;
@@ -28,7 +31,7 @@ const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!;
const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY!; const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY!;
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!; const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!;
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!; const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;
const TELEMETRY_ENABLED = (process.env.TELEMETRY_ENABLED! !== 'false') && true; const TELEMETRY_ENABLED = process.env.TELEMETRY_ENABLED! !== 'false' && true;
export { export {
PORT, PORT,
@@ -52,6 +55,7 @@ export {
SENTRY_DSN, SENTRY_DSN,
SITE_URL, SITE_URL,
SMTP_HOST, SMTP_HOST,
SMTP_PORT,
SMTP_NAME, SMTP_NAME,
SMTP_USERNAME, SMTP_USERNAME,
SMTP_PASSWORD, SMTP_PASSWORD,
+3 -5
View File
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
@@ -37,7 +38,6 @@ export const login1 = async (req: Request, res: Response) => {
email email
}).select('+salt +verifier'); }).select('+salt +verifier');
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error('Failed to find user');
const server = new jsrp.server(); const server = new jsrp.server();
@@ -54,7 +54,6 @@ export const login1 = async (req: Request, res: Response) => {
serverBInt: bigintConversion.bigintToBuf(server.bInt) serverBInt: bigintConversion.bigintToBuf(server.bInt)
}; };
return res.status(200).send({ return res.status(200).send({
serverPublicKey, serverPublicKey,
salt: user.salt salt: user.salt
@@ -105,7 +104,7 @@ export const login2 = async (req: Request, res: Response) => {
res.cookie('jid', tokens.refreshToken, { res.cookie('jid', tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/token', path: '/token',
sameSite: "strict", sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false secure: NODE_ENV === 'production' ? true : false
}); });
@@ -149,7 +148,7 @@ export const logout = async (req: Request, res: Response) => {
res.cookie('jid', '', { res.cookie('jid', '', {
httpOnly: true, httpOnly: true,
path: '/token', path: '/token',
sameSite: "strict", sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false secure: NODE_ENV === 'production' ? true : false
}); });
} catch (err) { } catch (err) {
@@ -213,7 +212,6 @@ export const getNewToken = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
token token
}); });
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
@@ -217,7 +217,7 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
try { try {
const { email, code } = req.body; const { email, code } = req.body;
user = await User.findOne({ email }); user = await User.findOne({ email }).select('+publicKey');
if (user && user?.publicKey) { if (user && user?.publicKey) {
// case: user has already completed account // case: user has already completed account
return res.status(403).send({ return res.status(403).send({
@@ -257,7 +257,7 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
return res.status(400).send({ return res.status(400).send({
error: 'Failed email magic link confirmation' error: 'Failed email magic link verification for organization invitation'
}); });
} }
+177 -4
View File
@@ -1,11 +1,121 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import crypto from 'crypto';
const jsrp = require('jsrp'); const jsrp = require('jsrp');
import * as bigintConversion from 'bigint-conversion'; import * as bigintConversion from 'bigint-conversion';
import { User, BackupPrivateKey } from '../models'; import { User, Token, BackupPrivateKey } from '../models';
import { checkEmailVerification } from '../helpers/signup';
import { createToken } from '../helpers/auth';
import { sendMail } from '../helpers/nodemailer';
import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../config';
const clientPublicKeys: any = {}; const clientPublicKeys: any = {};
/**
* Password reset step 1: Send email verification link to email [email]
* for account recovery.
* @param req
* @param res
* @returns
*/
export const emailPasswordReset = async (req: Request, res: Response) => {
let email: string;
try {
email = req.body.email;
const user = await User.findOne({ email }).select('+publicKey');
if (!user || !user?.publicKey) {
// case: user has already completed account
return res.status(403).send({
error: 'Failed to send email verification for password reset'
});
}
const token = crypto.randomBytes(16).toString('hex');
await Token.findOneAndUpdate(
{ email },
{
email,
token,
createdAt: new Date()
},
{ upsert: true, new: true }
);
await sendMail({
template: 'passwordReset.handlebars',
subjectLine: 'Infisical password reset',
recipients: [email],
substitutions: {
email,
token,
callback_url: SITE_URL + '/password-reset'
}
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to send email for account recovery'
});
}
return res.status(200).send({
message: `Sent an email for account recovery to ${email}`
});
}
/**
* Password reset step 2: Verify email verification link sent to email [email]
* @param req
* @param res
* @returns
*/
export const emailPasswordResetVerify = async (req: Request, res: Response) => {
let user, token;
try {
const { email, code } = req.body;
user = await User.findOne({ email }).select('+publicKey');
if (!user || !user?.publicKey) {
// case: user doesn't exist with email [email] or
// hasn't even completed their account
return res.status(403).send({
error: 'Failed email verification for password reset'
});
}
await checkEmailVerification({
email,
code
});
// generate temporary password-reset token
token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: JWT_SIGNUP_LIFETIME,
secret: JWT_SIGNUP_SECRET
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed email verification for password reset'
});
}
return res.status(200).send({
message: 'Successfully verified email',
user,
token
});
}
/** /**
* Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol * Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol
* @param req * @param req
@@ -43,7 +153,7 @@ export const srp1 = async (req: Request, res: Response) => {
} }
); );
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser({ email: req.user.email });
Sentry.captureException(err); Sentry.captureException(err);
return res.status(400).send({ return res.status(400).send({
error: 'Failed to start change password process' error: 'Failed to start change password process'
@@ -110,7 +220,7 @@ export const changePassword = async (req: Request, res: Response) => {
} }
); );
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser({ email: req.user.email });
Sentry.captureException(err); Sentry.captureException(err);
return res.status(400).send({ return res.status(400).send({
error: 'Failed to change password. Try again?' error: 'Failed to change password. Try again?'
@@ -180,10 +290,73 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
} }
); );
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser({ email: req.user.email });
Sentry.captureException(err); Sentry.captureException(err);
return res.status(400).send({ return res.status(400).send({
message: 'Failed to update backup private key' message: 'Failed to update backup private key'
}); });
} }
}; };
/**
* Return backup private key for user
* @param req
* @param res
* @returns
*/
export const getBackupPrivateKey = async (req: Request, res: Response) => {
let backupPrivateKey;
try {
backupPrivateKey = await BackupPrivateKey.findOne({
user: req.user._id
});
if (!backupPrivateKey) throw new Error('Failed to find backup private key');
} catch (err) {
Sentry.setUser({ email: req.user.email});
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get backup private key'
});
}
return res.status(200).send({
backupPrivateKey
});
}
export const resetPassword = async (req: Request, res: Response) => {
try {
const {
encryptedPrivateKey,
iv,
tag,
salt,
verifier,
} = req.body;
await User.findByIdAndUpdate(
req.user._id.toString(),
{
encryptedPrivateKey,
iv,
tag,
salt,
verifier
},
{
new: true
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email});
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get backup private key'
});
}
return res.status(200).send({
message: 'Successfully reset password'
});
}
+29 -9
View File
@@ -2,21 +2,40 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import handlebars from 'handlebars'; import handlebars from 'handlebars';
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import { SMTP_HOST, SMTP_NAME, SMTP_USERNAME, SMTP_PASSWORD } from '../config'; import {
SMTP_HOST,
SMTP_PORT,
SMTP_NAME,
SMTP_USERNAME,
SMTP_PASSWORD
} from '../config';
import SMTPConnection from 'nodemailer/lib/smtp-connection';
import * as Sentry from '@sentry/node';
// create nodemailer transporter const mailOpts: SMTPConnection.Options = {
const transporter = nodemailer.createTransport({
host: SMTP_HOST, host: SMTP_HOST,
port: 587, port: SMTP_PORT as number
auth: { };
if (SMTP_USERNAME && SMTP_PASSWORD) {
mailOpts.auth = {
user: SMTP_USERNAME, user: SMTP_USERNAME,
pass: SMTP_PASSWORD pass: SMTP_PASSWORD
};
} }
}); // create nodemailer transporter
const transporter = nodemailer.createTransport(mailOpts);
transporter transporter
.verify() .verify()
.then(() => console.log('SMTP - Successfully connected')) .then(() => {
.catch((err) => console.log('SMTP - Failed to connect')); Sentry.setUser(null);
Sentry.captureMessage('SMTP - Successfully connected');
})
.catch((err) => {
Sentry.setUser(null);
Sentry.captureException(
`SMTP - Failed to connect to ${SMTP_HOST}:${SMTP_PORT} \n\t${err}`
);
});
/** /**
* @param {Object} obj * @param {Object} obj
@@ -51,7 +70,8 @@ const sendMail = async ({
html: htmlToSend html: htmlToSend
}); });
} catch (err) { } catch (err) {
console.error(err); Sentry.setUser(null);
Sentry.captureException(err);
} }
}; };
+1 -1
View File
@@ -33,7 +33,7 @@ const sendEmailVerification = async ({ email }: { email: string }) => {
// send mail // send mail
await sendMail({ await sendMail({
template: 'emailVerification.handlebars', template: 'emailVerification.handlebars',
subjectLine: 'Infisical workspace invitation', subjectLine: 'Infisical confirmation code',
recipients: [email], recipients: [email],
substitutions: { substitutions: {
code: token code: token
+1 -5
View File
@@ -5,11 +5,7 @@ import { requireAuth, validateRequest } from '../middleware';
import { authController } from '../controllers'; import { authController } from '../controllers';
import { loginLimiter } from '../helpers/rateLimiter'; import { loginLimiter } from '../helpers/rateLimiter';
router.post( router.post('/token', validateRequest, authController.getNewToken);
'/token',
validateRequest,
authController.getNewToken
);
router.post( router.post(
'/login1', '/login1',
+41 -1
View File
@@ -1,7 +1,7 @@
import express from 'express'; import express from 'express';
const router = express.Router(); const router = express.Router();
import { body } from 'express-validator'; import { body } from 'express-validator';
import { requireAuth, validateRequest } from '../middleware'; import { requireAuth, requireSignupAuth, validateRequest } from '../middleware';
import { passwordController } from '../controllers'; import { passwordController } from '../controllers';
import { passwordLimiter } from '../helpers/rateLimiter'; import { passwordLimiter } from '../helpers/rateLimiter';
@@ -27,6 +27,33 @@ router.post(
passwordController.changePassword passwordController.changePassword
); );
// NEW
router.post(
'/email/password-reset',
passwordLimiter,
body('email').exists().trim().notEmpty(),
validateRequest,
passwordController.emailPasswordReset
);
// NEW
router.post(
'/email/password-reset-verify',
passwordLimiter,
body('email').exists().trim().notEmpty().isEmail(),
body('code').exists().trim().notEmpty(),
validateRequest,
passwordController.emailPasswordResetVerify
);
// NEW
router.get(
'/backup-private-key',
passwordLimiter,
requireSignupAuth,
passwordController.getBackupPrivateKey
);
router.post( router.post(
'/backup-private-key', '/backup-private-key',
passwordLimiter, passwordLimiter,
@@ -41,4 +68,17 @@ router.post(
passwordController.createBackupPrivateKey passwordController.createBackupPrivateKey
); );
// NEW
router.post(
'/password-reset',
requireSignupAuth,
body('encryptedPrivateKey').exists().trim().notEmpty(), // private key encrypted under new pwd
body('iv').exists().trim().notEmpty(), // new iv for private key
body('tag').exists().trim().notEmpty(), // new tag for private key
body('salt').exists().trim().notEmpty(), // part of new pwd
body('verifier').exists().trim().notEmpty(), // part of new pwd
validateRequest,
passwordController.resetPassword
);
export default router; export default router;
@@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge"> <meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Email Verification</title> <title>Organization Invitation</title>
</head> </head>
<body> <body>
<h2>Infisical</h2> <h2>Infisical</h2>
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Account Recovery</title>
</head>
<body>
<h2>Infisical</h2>
<h2>Reset your password</h2>
<p>Someone requested a password reset.</p>
<a href="{{callback_url}}?token={{token}}&to={{email}}">Reset password</a>
<p>If you didn't initiate this request, please contact us immediately at [email protected]</p>
</body>
</html>
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge"> <meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Email Verification</title> <title>Project Invitation</title>
</head> </head>
<body> <body>
<h2>Infisical</h2> <h2>Infisical</h2>
+13
View File
@@ -20,6 +20,7 @@ services:
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
- mongo - mongo
- smtp-server
build: build:
context: ./backend context: ./backend
dockerfile: Dockerfile dockerfile: Dockerfile
@@ -84,6 +85,18 @@ services:
networks: networks:
- infisical-dev - infisical-dev
smtp-server:
container_name: infisical-dev-smtp-server
image: mailhog/mailhog
restart: always
logging:
driver: 'none' # disable saving logs
ports:
- 1025:1025 # SMTP server
- 8025:8025 # Web UI
networks:
- infisical-dev
volumes: volumes:
mongo-data: mongo-data:
driver: local driver: local
+11 -16
View File
@@ -1,5 +1,5 @@
--- ---
title: "Install" title: 'Install'
--- ---
Prerequisite: Set up an account with [Infisical Cloud](https://app.infisical.com) or via a [self-hosted installation](/self-hosting/overview). Prerequisite: Set up an account with [Infisical Cloud](https://app.infisical.com) or via a [self-hosted installation](/self-hosting/overview).
@@ -13,11 +13,7 @@ The Infisical CLI provides a way to inject environment variables from the platfo
Use [brew](https://brew.sh/) package manager Use [brew](https://brew.sh/) package manager
```bash ```bash
# install
brew install infisical/get-cli/infisical brew install infisical/get-cli/infisical
# check version
infisical --version
``` ```
## Updates ## Updates
@@ -31,12 +27,11 @@ The Infisical CLI provides a way to inject environment variables from the platfo
Use [Scoop](https://scoop.sh/) package manager Use [Scoop](https://scoop.sh/) package manager
```bash ```bash
# install
scoop bucket add org https://github.com/Infisical/scoop-infisical.git scoop bucket add org https://github.com/Infisical/scoop-infisical.git
scoop install infisical ```
# check version ```bash
infisical --version scoop install infisical
``` ```
## Updates ## Updates
@@ -49,33 +44,33 @@ The Infisical CLI provides a way to inject environment variables from the platfo
<Tab title="Alpine"> <Tab title="Alpine">
Install prerequisite Install prerequisite
```bash ```bash
$ sudo apk add --no-cache bash sudo sudo apk add --no-cache bash sudo
``` ```
Add Infisical repository Add Infisical repository
```bash ```bash
$ curl -1sLf \ curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' \ 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' \
| sudo -E bash | sudo -E bash
``` ```
Then install CLI Then install CLI
```bash ```bash
$ sudo apk update && sudo apk add infisical sudo apk update && sudo apk add infisical
``` ```
</Tab> </Tab>
<Tab title="RedHat/CentOs/Amazon"> <Tab title="RedHat/CentOs/Amazon">
Add Infisical repository Add Infisical repository
```bash ```bash
$ curl -1sLf \ curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.rpm.sh' \ 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.rpm.sh' \
| sudo -E bash | sudo -E bash
``` ```
Then install CLI Then install CLI
```bash ```bash
$ sudo yum install infisical sudo yum install infisical
``` ```
</Tab> </Tab>
@@ -83,14 +78,14 @@ The Infisical CLI provides a way to inject environment variables from the platfo
Add Infisical repository Add Infisical repository
```bash ```bash
$ curl -1sLf \ curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' \ 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' \
| sudo -E bash | sudo -E bash
``` ```
Then install CLI Then install CLI
```bash ```bash
$ sudo apt-get update && sudo apt-get install -y infisical sudo apt-get update && sudo apt-get install -y infisical
``` ```
</Tab> </Tab>
+4 -2
View File
@@ -1,10 +1,12 @@
--- ---
title: "Frequently Asked Questions" 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)." 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 ## 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 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. 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.
+54 -6
View File
@@ -1,6 +1,6 @@
--- ---
title: "Developing" title: 'Developing'
description: "This guide will help you set up and run Infisical in development mode." description: 'This guide will help you set up and run Infisical in development mode.'
--- ---
## Clone the repo ## Clone the repo
@@ -16,17 +16,65 @@ cd infisical
## Set up environment variables ## Set up environment variables
Tweak the `.env` according to your preferences. Refer to the available [environment variables](/self-hosting/configuration/envars). Before running the docker-compose we have to generate the .env file with the environment variables, you can create your own file or start with the
`.env.example` as an example guide.
Mandatory variables in the `.env` file:
1. Keys and JWT variables
![image](https://user-images.githubusercontent.com/118568289/206791534-9c9d1431-e83d-49c0-8a54-b373ed0df820.png)
The `.env.example` has these variables empty, you can self generate the `JWT and ENCRYPTION_KEY` with this [32-byte random hex strings generator](https://www.browserling.com/tools/random-hex).
For the `PRIVATE_KEY and PUBLIC_KEY` you can use the ones shown in the screenshot:
```bash
cp .env.example .env
``` ```
PRIVATE_KEY='oGVv5rThrpZ7WLgQW27chY1cXngr4wLQIZnGfSKgHPk='
PUBLIC_KEY='ldr6JaC7AY+tun3omGLdE4SWpkJbtVBOI54KfUP53Xc='
```
2. Mongo variables and site URL
![image](https://user-images.githubusercontent.com/118568289/206792171-3376e3c6-c3ac-4d5d-8776-d78ee089b520.png)
These variables are used to connect the MongoDB and set the URL for the localhost.
For development, you can use `root` for the `MONGO_USERNAME` and `example` for the `MONGO_PASSWORD` as shown in the screenshot.
Take into account that if you use your own `MONGO_USERNAME` and `MONGO_PASSWORD`, you also have to change the `MONGO_URL` with the form of `MONGO_USERNAME:MONGO_PASSWORD` after the `//` part of the URL.
3. Mail SMTP service variables
![image](https://user-images.githubusercontent.com/118568289/206792653-ba3211d1-1071-43f2-93a7-8b408bbd9e0e.png)
If you want to receive actual emails (e.g. you want to test how the email message will look like), take note of the following.
For the `SMTP_USERNAME` variable, you will need an email with 2-steps-verification.
For the `SMTP_PASSWORD` variable, you will need to [generate an app password](https://support.google.com/mail/answer/185833?hl=en) with the email you used in the `SMTP_USERNAME` variable.
Otherwise, a local SMTP server (MailHog) is available for testing purposes. Set the following values to use this:
```
SMTP_HOST=smtp-server
SMTP_PORT=1025
SMTP_NAME=<whatever you like>
[email protected]
SMTP_PASSWORD=
```
Make sure to leave the `SMTP_PASSWORD` blank so the backend will be able to connect to MailHog
You can browse `http://localhost:8025/` to browse email messages sent by the backend.
With these environment variables, you will be ready to run the docker-compose.
## Docker for development ## Docker for development
```bash ```bash
# build and start the services # build and start the services
docker-compose -f docker-compose.dev.yml up --build docker-compose -f docker-compose.dev.yml up --build --force-recreate
``` ```
Then browse http://localhost:8080 Then browse http://localhost:8080
+2 -2
View File
@@ -9,13 +9,13 @@
"plugins": ["simple-import-sort", "@typescript-eslint"], "plugins": ["simple-import-sort", "@typescript-eslint"],
"rules": { "rules": {
"react-hooks/exhaustive-deps": "off", "react-hooks/exhaustive-deps": "off",
"no-unused-vars": "off", "no-unused-vars": "warn",
"@typescript-eslint/ban-ts-comment": "warn",
"@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-var-requires": "off", "@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-non-null-assertion": "off",
"simple-import-sort/exports": "warn", "simple-import-sort/exports": "warn",
"simple-import-sort/imports": [ "simple-import-sort/imports": [
"warn", "warn",
-4
View File
@@ -1,4 +0,0 @@
{
"tabWidth": 2,
"useTabs": false
}
+14 -14
View File
@@ -1,9 +1,9 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react';
import Image from "next/image"; import Image from 'next/image';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { publicPaths } from "~/const"; import { publicPaths } from '~/const';
import checkAuth from "~/pages/api/auth/CheckAuth"; import checkAuth from '~/pages/api/auth/CheckAuth';
// #TODO: finish spinner only when the data loads fully // #TODO: finish spinner only when the data loads fully
// #TODO: Redirect somewhere if the page does not exist // #TODO: Redirect somewhere if the page does not exist
@@ -22,16 +22,16 @@ export default function RouteGuard({ children }) {
// #TODO: add the loading page when not yet authorized. // #TODO: add the loading page when not yet authorized.
const hideContent = () => setAuthorized(false); const hideContent = () => setAuthorized(false);
// const onError = () => setAuthorized(true) // const onError = () => setAuthorized(true)
router.events.on("routeChangeStart", hideContent); router.events.on('routeChangeStart', hideContent);
// router.events.on("routeChangeError", onError); // router.events.on("routeChangeError", onError);
// on route change complete - run auth check // on route change complete - run auth check
router.events.on("routeChangeComplete", authCheck); router.events.on('routeChangeComplete', authCheck);
// unsubscribe from events in useEffect return function // unsubscribe from events in useEffect return function
return () => { return () => {
router.events.off("routeChangeStart", hideContent); router.events.off('routeChangeStart', hideContent);
router.events.off("routeChangeComplete", authCheck); router.events.off('routeChangeComplete', authCheck);
// router.events.off("routeChangeError", onError); // router.events.off("routeChangeError", onError);
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -43,7 +43,7 @@ export default function RouteGuard({ children }) {
*/ */
async function authCheck(url) { async function authCheck(url) {
// Make sure that we don't redirect when the user is on the following pages. // Make sure that we don't redirect when the user is on the following pages.
const path = "/" + url.split("?")[0].split("/")[1]; const path = '/' + url.split('?')[0].split('/')[1];
// Check if the user is authenticated // Check if the user is authenticated
const response = await checkAuth(); const response = await checkAuth();
@@ -51,16 +51,16 @@ export default function RouteGuard({ children }) {
if (!publicPaths.includes(path)) { if (!publicPaths.includes(path)) {
try { try {
if (response.status !== 200) { if (response.status !== 200) {
router.push("/login"); router.push('/login');
console.log("Unauthorized to access."); console.log('Unauthorized to access.');
setAuthorized(false); setAuthorized(false);
} else { } else {
setAuthorized(true); setAuthorized(true);
console.log("Authorized to access."); console.log('Authorized to access.');
} }
} catch (error) { } catch (error) {
console.log( console.log(
"Error (probably the authCheck route is stuck again...):", 'Error (probably the authCheck route is stuck again...):',
error error
); );
} }
+6 -9
View File
@@ -1,16 +1,13 @@
import posthog from "posthog-js"; import posthog from 'posthog-js';
import { import { ENV, POSTHOG_API_KEY, POSTHOG_HOST } from '../utilities/config';
ENV,
POSTHOG_API_KEY,
POSTHOG_HOST,
} from "../utilities/config";
export const initPostHog = () => { export const initPostHog = () => {
if (typeof window !== "undefined") { if (typeof window !== 'undefined') {
if (ENV == "production" && TELEMETRY_CAPTURING_ENABLED) { // eslint-disable-line // eslint-disable-next-line
if (ENV == 'production' && TELEMETRY_CAPTURING_ENABLED) {
posthog.init(POSTHOG_API_KEY, { posthog.init(POSTHOG_API_KEY, {
api_host: POSTHOG_HOST, api_host: POSTHOG_HOST
}); });
} }
} }
+18
View File
@@ -0,0 +1,18 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-undef */
import posthog from 'posthog-js';
import { ENV, POSTHOG_API_KEY, POSTHOG_HOST } from '../utilities/config';
export const initPostHog = () => {
if (typeof window !== 'undefined') {
// @ts-ignore
if (ENV == 'production' && TELEMETRY_CAPTURING_ENABLED) {
posthog.init(POSTHOG_API_KEY, {
api_host: POSTHOG_HOST
});
}
}
return posthog;
};
+13 -13
View File
@@ -1,9 +1,9 @@
import React, { useState } from "react"; import React, { useState } from 'react';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faCircle, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons"; import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import guidGenerator from "../utilities/randomId"; import guidGenerator from '../utilities/randomId';
interface InputFieldProps { interface InputFieldProps {
static?: boolean; static?: boolean;
@@ -23,7 +23,7 @@ interface InputFieldProps {
const InputField = ( const InputField = (
props: InputFieldProps & props: InputFieldProps &
Pick<JSX.IntrinsicElements["input"], "autoComplete" | "id"> Pick<JSX.IntrinsicElements['input'], 'autoComplete' | 'id'>
) => { ) => {
const [passwordVisible, setPasswordVisible] = useState(false); const [passwordVisible, setPasswordVisible] = useState(false);
const router = useRouter(); const router = useRouter();
@@ -75,28 +75,28 @@ const InputField = (
</div> </div>
<div <div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${ className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
props.error ? "border-red" : "border-mineshaft-500" props.error ? 'border-red' : 'border-mineshaft-500'
} rounded-md`} } rounded-md`}
> >
<input <input
onChange={(e) => props.onChangeHandler(e.target.value)} onChange={(e) => props.onChangeHandler(e.target.value)}
type={passwordVisible === false ? props.type : "text"} type={passwordVisible === false ? props.type : 'text'}
placeholder={props.placeholder} placeholder={props.placeholder}
value={props.value} value={props.value}
required={props.isRequired} required={props.isRequired}
className={`${ className={`${
props.blurred props.blurred
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400" ? 'text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400'
: "" : ''
} ${ } ${
props.error ? "focus:ring-red/50" : "focus:ring-primary/50" props.error ? 'focus:ring-red/50' : 'focus:ring-primary/50'
} relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`} } relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
name={props.name} name={props.name}
spellCheck="false" spellCheck="false"
autoComplete={props.autoComplete} autoComplete={props.autoComplete}
id={props.id} id={props.id}
/> />
{props.label?.includes("Password") && ( {props.label?.includes('Password') && (
<button <button
onClick={() => { onClick={() => {
setPasswordVisible(!passwordVisible); setPasswordVisible(!passwordVisible);
@@ -114,7 +114,7 @@ const InputField = (
<div className="peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden"> <div className="peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
<p className="ml-2"></p> <p className="ml-2"></p>
{props.value {props.value
.split("") .split('')
.slice(0, 54) .slice(0, 54)
.map(() => ( .map(() => (
<FontAwesomeIcon <FontAwesomeIcon
+97 -123
View File
@@ -1,37 +1,38 @@
/* eslint-disable no-unexpected-multiline */ /* eslint-disable no-unexpected-multiline */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react';
import Link from "next/link"; import Link from 'next/link';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { import {
faBookOpen, faBookOpen,
faGear, faGear,
faKey, faKey,
faMobile, faMobile,
faPlug, faPlug,
faUser, faUser
} from "@fortawesome/free-solid-svg-icons"; } from '@fortawesome/free-solid-svg-icons';
import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import getOrganizations from "~/pages/api/organization/getOrgs"; import getOrganizations from '~/pages/api/organization/getOrgs';
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects"; import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects';
import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers"; import getOrganizationUsers from '~/pages/api/organization/GetOrgUsers';
import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace"; import checkUserAction from '~/pages/api/userActions/checkUserAction';
import createWorkspace from "~/pages/api/workspace/createWorkspace"; import addUserToWorkspace from '~/pages/api/workspace/addUserToWorkspace';
import getWorkspaces from "~/pages/api/workspace/getWorkspaces"; import createWorkspace from '~/pages/api/workspace/createWorkspace';
import uploadKeys from "~/pages/api/workspace/uploadKeys"; import getWorkspaces from '~/pages/api/workspace/getWorkspaces';
import checkUserAction from "~/pages/api/userActions/checkUserAction"; import uploadKeys from '~/pages/api/workspace/uploadKeys';
import NavBarDashboard from "../navigation/NavBarDashboard"; import NavBarDashboard from '../navigation/NavBarDashboard';
import { tempLocalStorage } from "../utilities/checks/tempLocalStorage"; import onboardingCheck from '../utilities/checks/OnboardingCheck';
import { tempLocalStorage } from '../utilities/checks/tempLocalStorage';
import { import {
decryptAssymmetric, decryptAssymmetric,
encryptAssymmetric, encryptAssymmetric
} from "../utilities/cryptography/crypto"; } from '../utilities/cryptography/crypto';
import Button from "./buttons/Button"; import Button from './buttons/Button';
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog"; import AddWorkspaceDialog from './dialog/AddWorkspaceDialog';
import Listbox from "./Listbox"; import Listbox from './Listbox';
interface LayoutProps { interface LayoutProps {
children: React.ReactNode; children: React.ReactNode;
@@ -41,16 +42,13 @@ export default function Layout({ children }: LayoutProps) {
const router = useRouter(); const router = useRouter();
const [workspaceList, setWorkspaceList] = useState([]); const [workspaceList, setWorkspaceList] = useState([]);
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]); const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
const [workspaceSelected, setWorkspaceSelected] = useState("∞"); const [workspaceSelected, setWorkspaceSelected] = useState('∞');
const [newWorkspaceName, setNewWorkspaceName] = useState(""); const [newWorkspaceName, setNewWorkspaceName] = useState('');
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [hasUserClickedSlack, setHasUserClickedSlack] = useState(false); const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] =
const [hasUserClickedIntro, setHasUserClickedIntro] = useState(false); useState(0);
const [hasUserStarred, setHasUserStarred] = useState(false);
const [usersInOrg, setUsersInOrg] = useState(false);
const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] = useState(0);
function closeModal() { function closeModal() {
setIsOpen(false); setIsOpen(false);
@@ -77,35 +75,35 @@ export default function Layout({ children }: LayoutProps) {
if (!currentWorkspaces.includes(workspaceName)) { if (!currentWorkspaces.includes(workspaceName)) {
const newWorkspace = await createWorkspace({ const newWorkspace = await createWorkspace({
workspaceName, workspaceName,
organizationId: tempLocalStorage("orgData.id"), organizationId: tempLocalStorage('orgData.id')
}); });
const newWorkspaceId = newWorkspace._id; const newWorkspaceId = newWorkspace._id;
if (addAllUsers) { if (addAllUsers) {
const orgUsers = await getOrganizationUsers({ const orgUsers = await getOrganizationUsers({
orgId: tempLocalStorage("orgData.id"), orgId: tempLocalStorage('orgData.id')
}); });
orgUsers.map(async (user: any) => { orgUsers.map(async (user: any) => {
if (user.status == "accepted") { if (user.status == 'accepted') {
const result = await addUserToWorkspace( const result = await addUserToWorkspace(
user.user.email, user.user.email,
newWorkspaceId newWorkspaceId
); );
if (result?.invitee && result?.latestKey) { if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY = tempLocalStorage("PRIVATE_KEY"); const PRIVATE_KEY = tempLocalStorage('PRIVATE_KEY');
// assymmetrically decrypt symmetric key with local private key // assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({ const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey, ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce, nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey, publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY, privateKey: PRIVATE_KEY
}); });
const { ciphertext, nonce } = encryptAssymmetric({ const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key, plaintext: key,
publicKey: result.invitee.publicKey, publicKey: result.invitee.publicKey,
privateKey: PRIVATE_KEY, privateKey: PRIVATE_KEY
}) as { ciphertext: string; nonce: string }; }) as { ciphertext: string; nonce: string };
uploadKeys( uploadKeys(
@@ -118,11 +116,11 @@ export default function Layout({ children }: LayoutProps) {
} }
}); });
} }
router.push("/dashboard/" + newWorkspaceId + "?Development"); router.push('/dashboard/' + newWorkspaceId + '?Development');
setIsOpen(false); setIsOpen(false);
setNewWorkspaceName(""); setNewWorkspaceName('');
} else { } else {
console.error("A project with this name already exists."); console.error('A project with this name already exists.');
setError(true); setError(true);
setLoading(false); setLoading(false);
} }
@@ -136,59 +134,59 @@ export default function Layout({ children }: LayoutProps) {
const menuItems = [ const menuItems = [
{ {
href: href:
"/dashboard/" + '/dashboard/' +
workspaceMapping[workspaceSelected as any] + workspaceMapping[workspaceSelected as any] +
"?Development", '?Development',
title: "Secrets", title: 'Secrets',
emoji: <FontAwesomeIcon icon={faKey} />, emoji: <FontAwesomeIcon icon={faKey} />
}, },
{ {
href: "/users/" + workspaceMapping[workspaceSelected as any], href: '/users/' + workspaceMapping[workspaceSelected as any],
title: "Members", title: 'Members',
emoji: <FontAwesomeIcon icon={faUser} />, emoji: <FontAwesomeIcon icon={faUser} />
}, },
{ {
href: "/integrations/" + workspaceMapping[workspaceSelected as any], href: '/integrations/' + workspaceMapping[workspaceSelected as any],
title: "Integrations", title: 'Integrations',
emoji: <FontAwesomeIcon icon={faPlug} />, emoji: <FontAwesomeIcon icon={faPlug} />
}, },
{ {
href: "/settings/project/" + workspaceMapping[workspaceSelected as any], href: '/settings/project/' + workspaceMapping[workspaceSelected as any],
title: "Project Settings", title: 'Project Settings',
emoji: <FontAwesomeIcon icon={faGear} />, emoji: <FontAwesomeIcon icon={faGear} />
}, }
]; ];
useEffect(() => { useEffect(() => {
// Put a user in a workspace if they're not in one yet // Put a user in a workspace if they're not in one yet
const putUserInWorkSpace = async () => { const putUserInWorkSpace = async () => {
if (tempLocalStorage("orgData.id") === "") { if (tempLocalStorage('orgData.id') === '') {
const userOrgs = await getOrganizations(); const userOrgs = await getOrganizations();
localStorage.setItem("orgData.id", userOrgs[0]._id); localStorage.setItem('orgData.id', userOrgs[0]._id);
} }
const orgUserProjects = await getOrganizationUserProjects({ const orgUserProjects = await getOrganizationUserProjects({
orgId: tempLocalStorage("orgData.id"), orgId: tempLocalStorage('orgData.id')
}); });
const userWorkspaces = orgUserProjects; const userWorkspaces = orgUserProjects;
if ( if (
userWorkspaces.length == 0 && userWorkspaces.length == 0 &&
router.asPath != "/noprojects" && router.asPath != '/noprojects' &&
!router.asPath.includes("settings") !router.asPath.includes('settings')
) { ) {
router.push("/noprojects"); router.push('/noprojects');
} else if (router.asPath != "/noprojects") { } else if (router.asPath != '/noprojects') {
const intendedWorkspaceId = router.asPath const intendedWorkspaceId = router.asPath
.split("/") .split('/')
[router.asPath.split("/").length - 1].split("?")[0]; [router.asPath.split('/').length - 1].split('?')[0];
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs // If a user is not a member of a workspace they are trying to access, just push them to one of theirs
if ( if (
intendedWorkspaceId != "heroku" && intendedWorkspaceId != 'heroku' &&
!userWorkspaces !userWorkspaces
.map((workspace: { _id: string }) => workspace._id) .map((workspace: { _id: string }) => workspace._id)
.includes(intendedWorkspaceId) .includes(intendedWorkspaceId)
) { ) {
router.push("/dashboard/" + userWorkspaces[0]._id + "?Development"); router.push('/dashboard/' + userWorkspaces[0]._id + '?Development');
} else { } else {
setWorkspaceList( setWorkspaceList(
userWorkspaces.map((workspace: any) => workspace.name) userWorkspaces.map((workspace: any) => workspace.name)
@@ -197,7 +195,7 @@ export default function Layout({ children }: LayoutProps) {
Object.fromEntries( Object.fromEntries(
userWorkspaces.map((workspace: any) => [ userWorkspaces.map((workspace: any) => [
workspace.name, workspace.name,
workspace._id, workspace._id
]) ])
) as any ) as any
); );
@@ -205,58 +203,19 @@ export default function Layout({ children }: LayoutProps) {
Object.fromEntries( Object.fromEntries(
userWorkspaces.map((workspace: any) => [ userWorkspaces.map((workspace: any) => [
workspace._id, workspace._id,
workspace.name, workspace.name
]) ])
)[ )[
router.asPath router.asPath
.split("/") .split('/')
[router.asPath.split("/").length - 1].split("?")[0] [router.asPath.split('/').length - 1].split('?')[0]
] ]
); );
} }
} }
}; };
putUserInWorkSpace(); putUserInWorkSpace();
onboardingCheck({ setTotalOnboardingActionsDone });
const checkUserActionsFunction = async () => {
let countActions = 0;
const userActionSlack = await checkUserAction({
action: "slack_cta_clicked",
});
setHasUserClickedSlack(userActionSlack ? true : false);
if (userActionSlack) {
countActions = countActions + 1;
}
const userActionIntro = await checkUserAction({
action: "intro_cta_clicked",
});
setHasUserClickedIntro(userActionIntro ? true : false);
if (userActionIntro) {
countActions = countActions + 1;
}
const userActionStar = await checkUserAction({
action: "star_cta_clicked",
});
setHasUserStarred(userActionStar ? true : false);
if (userActionStar) {
countActions = countActions + 1;
}
const orgId = localStorage.getItem("orgData.id");
const orgUsers = await getOrganizationUsers({
orgId: orgId ? orgId : "",
});
setUsersInOrg(orgUsers.length > 1)
if (orgUsers.length > 1) {
countActions = countActions + 1;
}
console.log(123, countActions)
setTotalOnboardingActionsDone(countActions);
};
console.log(`images/progress-${totalOnboardingActionsDone == 0 ? "0" : ""}${totalOnboardingActionsDone == 1 ? "14" : ""}${totalOnboardingActionsDone == 1 ? "28" : ""}${totalOnboardingActionsDone == 3 ? "43" : ""}${totalOnboardingActionsDone == 4 ? "57" : ""}.svg`)
checkUserActionsFunction();
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -265,16 +224,16 @@ export default function Layout({ children }: LayoutProps) {
workspaceMapping[Number(workspaceSelected)] && workspaceMapping[Number(workspaceSelected)] &&
`${workspaceMapping[Number(workspaceSelected)]}` !== `${workspaceMapping[Number(workspaceSelected)]}` !==
router.asPath router.asPath
.split("/") .split('/')
[router.asPath.split("/").length - 1].split("?")[0] [router.asPath.split('/').length - 1].split('?')[0]
) { ) {
router.push( router.push(
"/dashboard/" + '/dashboard/' +
workspaceMapping[Number(workspaceSelected)] + workspaceMapping[Number(workspaceSelected)] +
"?Development" '?Development'
); );
localStorage.setItem( localStorage.setItem(
"projectData.id", 'projectData.id',
`${workspaceMapping[Number(workspaceSelected)]}` `${workspaceMapping[Number(workspaceSelected)]}`
); );
} }
@@ -286,7 +245,10 @@ export default function Layout({ children }: LayoutProps) {
return ( return (
<> <>
<div className="fixed w-full hidden md:block flex flex-col h-screen"> <div className="fixed w-full hidden md:block flex flex-col h-screen">
<script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.2.2/cdn.js" defer></script> <script
src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.2.2/cdn.js"
defer
></script>
<NavBarDashboard /> <NavBarDashboard />
<div className="flex flex-col md:flex-row flex-1"> <div className="flex flex-col md:flex-row flex-1">
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen"> <aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
@@ -320,11 +282,11 @@ export default function Layout({ children }: LayoutProps) {
{workspaceList.length > 0 && {workspaceList.length > 0 &&
menuItems.map(({ href, title, emoji }) => ( menuItems.map(({ href, title, emoji }) => (
<li className="mt-0.5 mx-2" key={title}> <li className="mt-0.5 mx-2" key={title}>
{router.asPath.split("/")[1] === href.split("/")[1] && {router.asPath.split('/')[1] === href.split('/')[1] &&
(["project", "billing", "org", "personal"].includes( (['project', 'billing', 'org', 'personal'].includes(
router.asPath.split("/")[2] router.asPath.split('/')[2]
) )
? router.asPath.split("/")[2] === href.split("/")[2] ? router.asPath.split('/')[2] === href.split('/')[2]
: true) ? ( : true) ? (
<div <div
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`} className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
@@ -335,7 +297,7 @@ export default function Layout({ children }: LayoutProps) {
</p> </p>
{title} {title}
</div> </div>
) : router.asPath == "/noprojects" ? ( ) : router.asPath == '/noprojects' ? (
<div <div
className={`flex p-2.5 text-white text-sm rounded`} className={`flex p-2.5 text-white text-sm rounded`}
> >
@@ -361,7 +323,7 @@ export default function Layout({ children }: LayoutProps) {
</ul> </ul>
</div> </div>
<div className="w-full mt-40 mb-4 px-2"> <div className="w-full mt-40 mb-4 px-2">
{router.asPath.split("/")[1] === "home" ? ( {router.asPath.split('/')[1] === 'home' ? (
<div <div
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`} className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
> >
@@ -371,7 +333,13 @@ export default function Layout({ children }: LayoutProps) {
</p> </p>
Infisical Guide Infisical Guide
<img <img
src={`/images/progress-${totalOnboardingActionsDone == 0 ? "0" : ""}${totalOnboardingActionsDone == 1 ? "14" : ""}${totalOnboardingActionsDone == 1 ? "28" : ""}${totalOnboardingActionsDone == 3 ? "43" : ""}${totalOnboardingActionsDone == 4 ? "57" : ""}.svg`} src={`/images/progress-${
totalOnboardingActionsDone == 0 ? '0' : ''
}${totalOnboardingActionsDone == 1 ? '14' : ''}${
totalOnboardingActionsDone == 2 ? '28' : ''
}${totalOnboardingActionsDone == 3 ? '43' : ''}${
totalOnboardingActionsDone == 4 ? '57' : ''
}${totalOnboardingActionsDone == 5 ? '71' : ''}.svg`}
height={58} height={58}
width={58} width={58}
alt="progress bar" alt="progress bar"
@@ -390,7 +358,13 @@ export default function Layout({ children }: LayoutProps) {
</p> </p>
Infisical Guide Infisical Guide
<img <img
src="/images/progress-75.svg" src={`/images/progress-${
totalOnboardingActionsDone == 0 ? '0' : ''
}${totalOnboardingActionsDone == 1 ? '14' : ''}${
totalOnboardingActionsDone == 2 ? '28' : ''
}${totalOnboardingActionsDone == 3 ? '43' : ''}${
totalOnboardingActionsDone == 4 ? '57' : ''
}${totalOnboardingActionsDone == 5 ? '71' : ''}.svg`}
height={58} height={58}
width={58} width={58}
alt="progress bar" alt="progress bar"
@@ -420,9 +394,9 @@ export default function Layout({ children }: LayoutProps) {
className="text-gray-300 text-7xl mb-8" className="text-gray-300 text-7xl mb-8"
/> />
<p className="text-gray-200 px-6 text-center text-lg max-w-sm"> <p className="text-gray-200 px-6 text-center text-lg max-w-sm">
{" "} {' '}
To use Infisical, please log in through a device with larger To use Infisical, please log in through a device with larger
dimensions.{" "} dimensions.{' '}
</p> </p>
</div> </div>
</> </>
@@ -1,39 +1,39 @@
import { Fragment, useState } from "react"; import { Fragment, useState } from 'react';
import { useRouter } from "next/router"; import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons';
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Dialog, Transition } from '@headlessui/react';
import { Dialog, Transition } from "@headlessui/react"; import nacl from 'tweetnacl';
import nacl from "tweetnacl";
import addServiceToken from "~/pages/api/serviceToken/addServiceToken"; import addServiceToken from '~/pages/api/serviceToken/addServiceToken';
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
import { envMapping } from "../../../public/data/frequentConstants"; import { envMapping } from '../../../public/data/frequentConstants';
import { import {
decryptAssymmetric, decryptAssymmetric,
encryptAssymmetric, encryptAssymmetric
} from "../../utilities/cryptography/crypto"; } from '../../utilities/cryptography/crypto';
import Button from "../buttons/Button"; import Button from '../buttons/Button';
import InputField from "../InputField"; import InputField from '../InputField';
import ListBox from "../Listbox"; import ListBox from '../Listbox';
const expiryMapping = { const expiryMapping = {
"1 day": 86400, '1 day': 86400,
"7 days": 604800, '7 days': 604800,
"1 month": 2592000, '1 month': 2592000,
'6 months': 15552000,
'12 months': 31104000
}; };
const AddServiceTokenDialog = ({ const AddServiceTokenDialog = ({
isOpen, isOpen,
closeModal, closeModal,
workspaceId, workspaceId,
workspaceName, workspaceName
}) => { }) => {
const router = useRouter(); const [serviceToken, setServiceToken] = useState('');
const [serviceToken, setServiceToken] = useState(""); const [serviceTokenName, setServiceTokenName] = useState('');
const [serviceTokenName, setServiceTokenName] = useState(""); const [serviceTokenEnv, setServiceTokenEnv] = useState('Development');
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development"); const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState('1 day');
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
const [serviceTokenCopied, setServiceTokenCopied] = useState(false); const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const generateServiceToken = async () => { const generateServiceToken = async () => {
@@ -43,7 +43,7 @@ const AddServiceTokenDialog = ({
ciphertext: latestFileKey.latestKey.encryptedKey, ciphertext: latestFileKey.latestKey.encryptedKey,
nonce: latestFileKey.latestKey.nonce, nonce: latestFileKey.latestKey.nonce,
publicKey: latestFileKey.latestKey.sender.publicKey, publicKey: latestFileKey.latestKey.sender.publicKey,
privateKey: localStorage.getItem("PRIVATE_KEY"), privateKey: localStorage.getItem('PRIVATE_KEY')
}); });
// generate new public/private key pair // generate new public/private key pair
@@ -55,7 +55,7 @@ const AddServiceTokenDialog = ({
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({ const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
plaintext: key, plaintext: key,
publicKey, publicKey,
privateKey, privateKey
}); });
let newServiceToken = await addServiceToken({ let newServiceToken = await addServiceToken({
@@ -65,16 +65,16 @@ const AddServiceTokenDialog = ({
expiresIn: expiryMapping[serviceTokenExpiresIn], expiresIn: expiryMapping[serviceTokenExpiresIn],
publicKey, publicKey,
encryptedKey, encryptedKey,
nonce, nonce
}); });
const serviceToken = newServiceToken + "," + privateKey; const serviceToken = newServiceToken + ',' + privateKey;
setServiceToken(serviceToken); setServiceToken(serviceToken);
}; };
function copyToClipboard() { function copyToClipboard() {
// Get the text field // Get the text field
var copyText = document.getElementById("serviceToken"); var copyText = document.getElementById('serviceToken');
// Select the text field // Select the text field
copyText.select(); copyText.select();
@@ -91,8 +91,8 @@ const AddServiceTokenDialog = ({
const closeAddServiceTokenModal = () => { const closeAddServiceTokenModal = () => {
closeModal(); closeModal();
setServiceTokenName(""); setServiceTokenName('');
setServiceToken(""); setServiceToken('');
}; };
return ( return (
@@ -122,7 +122,7 @@ const AddServiceTokenDialog = ({
leaveFrom="opacity-100 scale-100" leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95" leaveTo="opacity-0 scale-95"
> >
{serviceToken == "" ? ( {serviceToken == '' ? (
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all"> <Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title <Dialog.Title
as="h3" as="h3"
@@ -155,12 +155,12 @@ const AddServiceTokenDialog = ({
selected={serviceTokenEnv} selected={serviceTokenEnv}
onChange={setServiceTokenEnv} onChange={setServiceTokenEnv}
data={[ data={[
"Development", 'Development',
"Staging", 'Staging',
"Production", 'Production',
"Testing", 'Testing'
]} ]}
width="full" isFull={true}
text="Environment: " text="Environment: "
/> />
</div> </div>
@@ -168,8 +168,14 @@ const AddServiceTokenDialog = ({
<ListBox <ListBox
selected={serviceTokenExpiresIn} selected={serviceTokenExpiresIn}
onChange={setServiceTokenExpiresIn} onChange={setServiceTokenExpiresIn}
data={["1 day", "7 days", "1 month"]} data={[
width="full" '1 day',
'7 days',
'1 month',
'6 months',
'12 months'
]}
isFull={true}
text="Expires in: " text="Expires in: "
/> />
</div> </div>
@@ -181,7 +187,7 @@ const AddServiceTokenDialog = ({
text="Add Service Token" text="Add Service Token"
textDisabled="Add Service Token" textDisabled="Add Service Token"
size="md" size="md"
active={serviceTokenName == "" ? false : true} active={serviceTokenName == '' ? false : true}
/> />
</div> </div>
</div> </div>
@@ -1,10 +1,10 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faX } from "@fortawesome/free-solid-svg-icons"; import { faX } from '@fortawesome/free-solid-svg-icons';
import { reverseEnvMapping } from "../../../public/data/frequentConstants"; import { reverseEnvMapping } from '../../../public/data/frequentConstants';
import guidGenerator from "../../utilities/randomId"; import guidGenerator from '../../utilities/randomId';
import Button from "../buttons/Button"; import Button from '../buttons/Button';
/** /**
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too. * This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
+37 -37
View File
@@ -1,25 +1,25 @@
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from 'react';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faX } from "@fortawesome/free-solid-svg-icons"; import { faX } from '@fortawesome/free-solid-svg-icons';
import deleteUserFromOrganization from "~/pages/api/organization/deleteUserFromOrganization"; import deleteUserFromOrganization from '~/pages/api/organization/deleteUserFromOrganization';
import changeUserRoleInWorkspace from "~/pages/api/workspace/changeUserRoleInWorkspace"; import changeUserRoleInWorkspace from '~/pages/api/workspace/changeUserRoleInWorkspace';
import deleteUserFromWorkspace from "~/pages/api/workspace/deleteUserFromWorkspace"; import deleteUserFromWorkspace from '~/pages/api/workspace/deleteUserFromWorkspace';
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
import uploadKeys from "~/pages/api/workspace/uploadKeys"; import uploadKeys from '~/pages/api/workspace/uploadKeys';
import guidGenerator from "../../utilities/randomId"; import guidGenerator from '../../utilities/randomId';
import Button from "../buttons/Button"; import Button from '../buttons/Button';
import Listbox from "../Listbox"; import Listbox from '../Listbox';
const { const {
decryptAssymmetric, decryptAssymmetric,
encryptAssymmetric, encryptAssymmetric
} = require("../../utilities/cryptography/crypto"); } = require('../../utilities/cryptography/crypto');
const nacl = require("tweetnacl"); const nacl = require('tweetnacl');
nacl.util = require("tweetnacl-util"); nacl.util = require('tweetnacl-util');
const roles = ["admin", "user"]; const roles = ['admin', 'user'];
/** /**
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too. * This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
@@ -36,13 +36,13 @@ const UserTable = ({
isOrg, isOrg,
onClick, onClick,
deleteUser, deleteUser,
setUserIdToBeDeleted, setUserIdToBeDeleted
}) => { }) => {
const [roleSelected, setRoleSelected] = useState( const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role)) Array(userData?.length).fill(userData.map((user) => user.role))
); );
const router = useRouter(); const router = useRouter();
const [myRole, setMyRole] = useState("member"); const [myRole, setMyRole] = useState('member');
// Delete the row in the table (e.g. a user) // Delete the row in the table (e.g. a user)
// #TODO: Add a pop-up that warns you that the user is going to be deleted. // #TODO: Add a pop-up that warns you that the user is going to be deleted.
@@ -57,7 +57,7 @@ const UserTable = ({
changeData(userData.filter((v, i) => i !== index)); changeData(userData.filter((v, i) => i !== index));
setRoleSelected([ setRoleSelected([
...roleSelected.slice(0, index), ...roleSelected.slice(0, index),
...roleSelected.slice(index + 1, userData?.length), ...roleSelected.slice(index + 1, userData?.length)
]); ]);
}; };
@@ -76,10 +76,10 @@ const UserTable = ({
status: userData[index].status, status: userData[index].status,
userId: userData[index].userId, userId: userData[index].userId,
membershipId: userData[index].membershipId, membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey, publicKey: userData[index].publicKey
}, }
], ],
...userData.slice(index + 1, userData?.length), ...userData.slice(index + 1, userData?.length)
]); ]);
}; };
@@ -90,20 +90,20 @@ const UserTable = ({
const grantAccess = async (id, publicKey) => { const grantAccess = async (id, publicKey) => {
let result = await getLatestFileKey({ workspaceId: router.query.id }); let result = await getLatestFileKey({ workspaceId: router.query.id });
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
// assymmetrically decrypt symmetric key with local private key // assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({ const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey, ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce, nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey, publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY, privateKey: PRIVATE_KEY
}); });
const { ciphertext, nonce } = encryptAssymmetric({ const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key, plaintext: key,
publicKey: publicKey, publicKey: publicKey,
privateKey: PRIVATE_KEY, privateKey: PRIVATE_KEY
}); });
uploadKeys(router.query.id, id, ciphertext, nonce); uploadKeys(router.query.id, id, ciphertext, nonce);
@@ -158,24 +158,24 @@ const UserTable = ({
</td> </td>
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700"> <td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
<div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center"> <div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center">
{row.status == "granted" && {row.status == 'granted' &&
((myRole == "admin" && row.role != "owner") || ((myRole == 'admin' && row.role != 'owner') ||
myRole == "owner") && myRole == 'owner') &&
myUser !== row.email ? ( myUser !== row.email ? (
<Listbox <Listbox
selected={row.role} selected={row.role}
onChange={(e) => handleRoleUpdate(index, e)} onChange={(e) => handleRoleUpdate(index, e)}
data={ data={
myRole == "owner" myRole == 'owner'
? ["owner", "admin", "member"] ? ['owner', 'admin', 'member']
: ["admin", "member"] : ['admin', 'member']
} }
text="Role: " text="Role: "
membershipId={row.membershipId} membershipId={row.membershipId}
/> />
) : ( ) : (
row.status != "invited" && row.status != 'invited' &&
row.status != "verified" && ( row.status != 'verified' && (
<Listbox <Listbox
selected={row.role} selected={row.role}
text="Role: " text="Role: "
@@ -183,8 +183,8 @@ const UserTable = ({
/> />
) )
)} )}
{(row.status == "invited" || {(row.status == 'invited' ||
row.status == "verified") && ( row.status == 'verified') && (
<div className="w-full pl-9"> <div className="w-full pl-9">
<Button <Button
onButtonPressed={() => onButtonPressed={() =>
@@ -199,7 +199,7 @@ const UserTable = ({
/> />
</div> </div>
)} )}
{row.status == "completed" && myUser !== row.email && ( {row.status == 'completed' && myUser !== row.email && (
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200"> <div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
<Button <Button
onButtonPressed={() => onButtonPressed={() =>
@@ -214,7 +214,7 @@ const UserTable = ({
</div> </div>
{myUser !== row.email && {myUser !== row.email &&
// row.role != "admin" && // row.role != "admin" &&
myRole != "member" ? ( myRole != 'member' ? (
<div className="opacity-50 hover:opacity-100 flex items-center"> <div className="opacity-50 hover:opacity-100 flex items-center">
<Button <Button
onButtonPressed={(e) => onButtonPressed={(e) =>
@@ -1,14 +1,29 @@
import React from "react"; import React from 'react';
import StripeRedirect from "~/pages/api/organization/StripeRedirect"; import StripeRedirect from '~/pages/api/organization/StripeRedirect';
export default function Plan({ plan }) { import { tempLocalStorage } from '../utilities/checks/tempLocalStorage';
interface Props {
plan: {
name: string;
price: string;
priceExplanation: string;
text: string;
subtext: string;
buttonTextMain: string;
buttonTextSecondary: string;
current: boolean;
};
}
export default function Plan({ plan }: Props) {
return ( return (
<div <div
className={`relative flex flex-col justify-between border border-2 min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${ className={`relative flex flex-col justify-between border-2 min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${
(plan.name != "Starter") & (plan.current == true) plan.name != 'Starter' && plan.current == true
? "border-primary" ? 'border-primary'
: "border-chicago-700" : 'border-chicago-700'
} }
`} `}
> >
@@ -36,7 +51,7 @@ export default function Plan({ plan }) {
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
{plan.current == false ? ( {plan.current == false ? (
<> <>
{plan.buttonTextMain == "Schedule a Demo" ? ( {plan.buttonTextMain == 'Schedule a Demo' ? (
<a href="/scheduledemo" target='_blank rel="noopener"'> <a href="/scheduledemo" target='_blank rel="noopener"'>
<div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max"> <div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max">
{plan.buttonTextMain} {plan.buttonTextMain}
@@ -45,15 +60,15 @@ export default function Plan({ plan }) {
) : ( ) : (
<div <div
className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 text-gray-400 font-semibold ${ className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 text-gray-400 font-semibold ${
plan.buttonTextMain == "Downgrade" plan.buttonTextMain == 'Downgrade'
? "hover:bg-red hover:text-white hover:border-red" ? 'hover:bg-red hover:text-white hover:border-red'
: "hover:bg-primary hover:text-black hover:border-primary" : 'hover:bg-primary hover:text-black hover:border-primary'
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`} } bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
> >
<button <button
onClick={() => onClick={() =>
StripeRedirect({ StripeRedirect({
orgId: localStorage.getItem("orgData.id"), orgId: tempLocalStorage('orgData.id')
}) })
} }
> >
@@ -61,7 +76,10 @@ export default function Plan({ plan }) {
</button> </button>
</div> </div>
)} )}
<a href="https://infisical.com/pricing" target='_blank rel="noopener"'> <a
href="https://infisical.com/pricing"
target='_blank rel="noopener"'
>
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5"> <div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
{plan.buttonTextSecondary} {plan.buttonTextSecondary}
</div> </div>
@@ -70,9 +88,9 @@ export default function Plan({ plan }) {
) : ( ) : (
<div <div
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${ className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
(plan.name != "Starter") & (plan.current == true) plan.name != 'Starter' && plan.current == true
? "bg-primary" ? 'bg-primary'
: "bg-chicago-700" : 'bg-chicago-700'
}`} }`}
> >
<p className="text-xs text-black font-semibold">CURRENT PLAN</p> <p className="text-xs text-black font-semibold">CURRENT PLAN</p>
@@ -1,9 +1,8 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from 'react';
import { faXmarkCircle } from "@fortawesome/free-solid-svg-icons"; import { faX } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classnames from "classnames";
import { Notification as NotificationType } from "./NotificationProvider"; import { Notification as NotificationType } from './NotificationProvider';
interface NotificationProps { interface NotificationProps {
notification: Required<NotificationType>; notification: Required<NotificationType>;
@@ -12,7 +11,7 @@ interface NotificationProps {
const Notification = ({ const Notification = ({
notification, notification,
clearNotification, clearNotification
}: NotificationProps) => { }: NotificationProps) => {
const timeout = useRef<number>(); const timeout = useRef<number>();
@@ -37,22 +36,29 @@ const Notification = ({
return ( return (
<div <div
className={classnames( className="relative w-full flex items-center justify-between px-4 py-6 rounded-md border border-bunker-500 pointer-events-auto bg-bunker-500"
"w-full flex items-center justify-between px-4 py-3 rounded pointer-events-auto",
{
"bg-green-600": notification.type === "success",
"bg-red-500": notification.type === "error",
"bg-blue-500": notification.type === "info",
}
)}
role="alert" role="alert"
> >
<p className="text-white text-sm font-bold">{notification.text}</p> {notification.type === 'error' && (
<div className="absolute w-full h-1 bg-red top-0 left-0 rounded-t-md"></div>
)}
{notification.type === 'success' && (
<div className="absolute w-full h-1 bg-green top-0 left-0 rounded-t-md"></div>
)}
{notification.type === 'info' && (
<div className="absolute w-full h-1 bg-yellow top-0 left-0 rounded-t-md"></div>
)}
<p className="text-bunker-200 text-sm font-semibold mt-0.5">
{notification.text}
</p>
<button <button
className="bg-white/5 rounded-lg p-3" className="rounded-lg"
onClick={() => clearNotification(notification.text)} onClick={() => clearNotification(notification.text)}
> >
<FontAwesomeIcon className="text-white" icon={faXmarkCircle} /> <FontAwesomeIcon
className="text-white w-4 h-3 hover:text-red"
icon={faX}
/>
</button> </button>
</div> </div>
); );
@@ -1,8 +1,8 @@
import { createContext, ReactNode, useContext, useState } from "react"; import { createContext, ReactNode, useContext, useState } from 'react';
import Notifications from "./Notifications"; import Notifications from './Notifications';
type NotificationType = "success" | "error" | "info"; type NotificationType = 'success' | 'error' | 'info';
export type Notification = { export type Notification = {
text: string; text: string;
@@ -15,7 +15,7 @@ type NotificationContextState = {
}; };
const NotificationContext = createContext<NotificationContextState>({ const NotificationContext = createContext<NotificationContextState>({
createNotification: () => console.log("createNotification not set!"), createNotification: () => console.log('createNotification not set!')
}); });
export const useNotificationContext = () => useContext(NotificationContext); export const useNotificationContext = () => useContext(NotificationContext);
@@ -37,8 +37,8 @@ const NotificationProvider = ({ children }: NotificationProviderProps) => {
const createNotification = ({ const createNotification = ({
text, text,
type = "success", type = 'success',
timeoutMs = 2000, timeoutMs = 5000
}: Notification) => { }: Notification) => {
const doesNotifExist = notifications.some((notif) => notif.text === text); const doesNotifExist = notifications.some((notif) => notif.text === text);
@@ -54,7 +54,7 @@ const NotificationProvider = ({ children }: NotificationProviderProps) => {
return ( return (
<NotificationContext.Provider <NotificationContext.Provider
value={{ value={{
createNotification, createNotification
}} }}
> >
<Notifications <Notifications
@@ -1,5 +1,5 @@
import Notification from "./Notification"; import Notification from './Notification';
import { Notification as NotificationType } from "./NotificationProvider"; import { Notification as NotificationType } from './NotificationProvider';
interface NoticationsProps { interface NoticationsProps {
notifications: Required<NotificationType>[]; notifications: Required<NotificationType>[];
@@ -8,14 +8,14 @@ interface NoticationsProps {
const Notifications = ({ const Notifications = ({
notifications, notifications,
clearNotification, clearNotification
}: NoticationsProps) => { }: NoticationsProps) => {
if (!notifications.length) { if (!notifications.length) {
return null; return null;
} }
return ( return (
<div className="hidden fixed z-50 md:flex md:flex-col-reverse bottom-1 gap-y-2 w-96 h-full right-1 pointer-events-none"> <div className="hidden fixed z-50 md:flex md:flex-col-reverse bottom-1 gap-y-2 w-96 h-full right-2 bottom-2 pointer-events-none">
{notifications.map((notif) => ( {notifications.map((notif) => (
<Notification <Notification
key={notif.text} key={notif.text}
@@ -1,8 +1,8 @@
import React, { SyntheticEvent, useRef } from "react"; import React, { SyntheticEvent, useRef } from 'react';
import { faCircle } from "@fortawesome/free-solid-svg-icons"; import { faCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import guidGenerator from "../utilities/randomId"; import guidGenerator from '../utilities/randomId';
const REGEX = /([$]{.*?})/g; const REGEX = /([$]{.*?})/g;
@@ -10,7 +10,7 @@ interface DashboardInputFieldProps {
position: number; position: number;
onChangeHandler: (value: string, position: number) => void; onChangeHandler: (value: string, position: number) => void;
value: string; value: string;
type: "varName" | "value"; type: 'varName' | 'value';
blurred: boolean; blurred: boolean;
duplicates: string[]; duplicates: string[];
} }
@@ -33,7 +33,7 @@ const DashboardInputField = ({
type, type,
value, value,
blurred, blurred,
duplicates, duplicates
}: DashboardInputFieldProps) => { }: DashboardInputFieldProps) => {
const ref = useRef<HTMLDivElement | null>(null); const ref = useRef<HTMLDivElement | null>(null);
const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => { const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
@@ -43,8 +43,8 @@ const DashboardInputField = ({
ref.current.scrollLeft = e.currentTarget.scrollLeft; ref.current.scrollLeft = e.currentTarget.scrollLeft;
}; };
if (type === "varName") { if (type === 'varName') {
const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != ""; const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != '';
const hasDuplicates = duplicates?.includes(value); const hasDuplicates = duplicates?.includes(value);
const error = startsWithNumber || hasDuplicates; const error = startsWithNumber || hasDuplicates;
@@ -52,7 +52,7 @@ const DashboardInputField = ({
<div className="flex-col w-full"> <div className="flex-col w-full">
<div <div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${ className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
error ? "border-red" : "border-mineshaft-500" error ? 'border-red' : 'border-mineshaft-500'
} rounded-md`} } rounded-md`}
> >
<input <input
@@ -62,7 +62,7 @@ const DashboardInputField = ({
type={type} type={type}
value={value} value={value}
className={`z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 ${ className={`z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 ${
error ? "focus:ring-red/50" : "focus:ring-primary/50" error ? 'focus:ring-red/50' : 'focus:ring-primary/50'
} duration-200`} } duration-200`}
spellCheck="false" spellCheck="false"
/> />
@@ -79,7 +79,7 @@ const DashboardInputField = ({
)} )}
</div> </div>
); );
} else if (type === "value") { } else if (type === 'value') {
return ( return (
<div className="flex-col w-full"> <div className="flex-col w-full">
<div <div
@@ -91,8 +91,8 @@ const DashboardInputField = ({
onScroll={syncScroll} onScroll={syncScroll}
className={`${ className={`${
blurred blurred
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent" ? 'text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent'
: "" : ''
} z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`} } z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
spellCheck="false" spellCheck="false"
/> />
@@ -100,8 +100,8 @@ const DashboardInputField = ({
ref={ref} ref={ref}
className={`${ className={`${
blurred blurred
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400" ? 'text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400'
: "" : ''
} absolute flex flex-row whitespace-pre font-mono z-0 ph-no-capture max-w-2xl overflow-x-scroll bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`} } absolute flex flex-row whitespace-pre font-mono z-0 ph-no-capture max-w-2xl overflow-x-scroll bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
> >
{value.split(REGEX).map((word, id) => { {value.split(REGEX).map((word, id) => {
@@ -112,7 +112,7 @@ const DashboardInputField = ({
<span className="ph-no-capture text-yellow-200/80"> <span className="ph-no-capture text-yellow-200/80">
{word.slice(2, word.length - 1)} {word.slice(2, word.length - 1)}
</span> </span>
{word.slice(word.length - 1, word.length) == "}" ? ( {word.slice(word.length - 1, word.length) == '}' ? (
<span className="ph-no-capture text-yellow"> <span className="ph-no-capture text-yellow">
{word.slice(word.length - 1, word.length)} {word.slice(word.length - 1, word.length)}
</span> </span>
@@ -135,7 +135,7 @@ const DashboardInputField = ({
{blurred && ( {blurred && (
<div className="absolute flex flex-row items-center z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible h-9 w-full max-w-2xl rounded-md text-gray-400/50 text-clip"> <div className="absolute flex flex-row items-center z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible h-9 w-full max-w-2xl rounded-md text-gray-400/50 text-clip">
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar"> <div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
{value.split("").map(() => ( {value.split('').map(() => (
<FontAwesomeIcon <FontAwesomeIcon
key={guidGenerator()} key={guidGenerator()}
className="text-xxs mx-0.5" className="text-xxs mx-0.5"
+17 -17
View File
@@ -1,12 +1,12 @@
import { type ChangeEvent, type DragEvent, useState } from "react"; import { type ChangeEvent, type DragEvent, useState } from 'react';
import Image from "next/image"; import Image from 'next/image';
import { faUpload } from "@fortawesome/free-solid-svg-icons"; import { faUpload } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from "../basic/buttons/Button"; import Button from '../basic/buttons/Button';
import Error from "../basic/Error"; import Error from '../basic/Error';
import parse from "../utilities/file"; import parse from '../utilities/file';
import guidGenerator from "../utilities/randomId"; import guidGenerator from '../utilities/randomId';
interface DropZoneProps { interface DropZoneProps {
// TODO: change Data type from any // TODO: change Data type from any
@@ -26,7 +26,7 @@ const DropZone = ({
errorDragAndDrop, errorDragAndDrop,
setButtonReady, setButtonReady,
keysExist, keysExist,
numCurrentRows, numCurrentRows
}: DropZoneProps) => { }: DropZoneProps) => {
const handleDragEnter = (e: DragEvent) => { const handleDragEnter = (e: DragEvent) => {
e.preventDefault(); e.preventDefault();
@@ -43,7 +43,7 @@ const DropZone = ({
e.stopPropagation(); e.stopPropagation();
// set dropEffect to copy i.e copy of the source item // set dropEffect to copy i.e copy of the source item
e.dataTransfer.dropEffect = "copy"; e.dataTransfer.dropEffect = 'copy';
}; };
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -54,7 +54,7 @@ const DropZone = ({
setTimeout(() => setLoading(false), 5000); setTimeout(() => setLoading(false), 5000);
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
e.dataTransfer.dropEffect = "copy"; e.dataTransfer.dropEffect = 'copy';
const file = e.dataTransfer.files[0]; const file = e.dataTransfer.files[0];
const reader = new FileReader(); const reader = new FileReader();
@@ -68,7 +68,7 @@ const DropZone = ({
numCurrentRows + index, numCurrentRows + index,
key, key,
keyPairs[key as keyof typeof keyPairs], keyPairs[key as keyof typeof keyPairs],
"shared", 'shared'
]); ]);
setData(newData); setData(newData);
setButtonReady(true); setButtonReady(true);
@@ -94,15 +94,15 @@ const DropZone = ({
reader.onload = (event) => { reader.onload = (event) => {
if (event.target === null || event.target.result === null) return; if (event.target === null || event.target.result === null) return;
const { result } = event.target; const { result } = event.target;
if (typeof result === "string") { if (typeof result === 'string') {
const newData = result const newData = result
.split("\n") .split('\n')
.map((line: string, index: number) => [ .map((line: string, index: number) => [
guidGenerator(), guidGenerator(),
numCurrentRows + index, numCurrentRows + index,
line.split("=")[0], line.split('=')[0],
line.split("=").slice(1, line.split("=").length).join("="), line.split('=').slice(1, line.split('=').length).join('='),
"shared", 'shared'
]); ]);
setData(newData); setData(newData);
setButtonReady(true); setButtonReady(true);
@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import React, { Fragment, useEffect, useState } from "react"; import React, { Fragment, useEffect, useState } from 'react';
import Image from "next/image"; import Image from 'next/image';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons"; import { faGithub, faSlack } from '@fortawesome/free-brands-svg-icons';
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons"; import { faCircleQuestion } from '@fortawesome/free-regular-svg-icons';
import { import {
faAngleDown, faAngleDown,
faBook, faBook,
@@ -12,39 +12,39 @@ import {
faEnvelope, faEnvelope,
faGear, faGear,
faPlus, faPlus,
faRightFromBracket, faRightFromBracket
} from "@fortawesome/free-solid-svg-icons"; } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from '@headlessui/react';
import logout from "~/pages/api/auth/Logout"; import logout from '~/pages/api/auth/Logout';
import getOrganization from "~/pages/api/organization/GetOrg"; import getOrganization from '~/pages/api/organization/GetOrg';
import getOrganizations from "~/pages/api/organization/getOrgs"; import getOrganizations from '~/pages/api/organization/getOrgs';
import getUser from "~/pages/api/user/getUser"; import getUser from '~/pages/api/user/getUser';
import guidGenerator from "../utilities/randomId"; import guidGenerator from '../utilities/randomId';
const supportOptions = [ const supportOptions = [
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
"Join Slack Forum", 'Join Slack Forum',
"https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g", 'https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g'
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
"Read Docs", 'Read Docs',
"https://infisical.com/docs/getting-started/introduction", 'https://infisical.com/docs/getting-started/introduction'
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
"Open a GitHub Issue", 'Open a GitHub Issue',
"https://github.com/Infisical/infisical-cli/issues", 'https://github.com/Infisical/infisical-cli/issues'
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
"Send us an Email", 'Send us an Email',
"mailto:[email protected]", 'mailto:[email protected]'
], ]
]; ];
export interface ICurrentOrg { export interface ICurrentOrg {
@@ -75,16 +75,16 @@ export default function Navbar() {
const orgsData = await getOrganizations(); const orgsData = await getOrganizations();
setOrgs(orgsData); setOrgs(orgsData);
const currentOrg = await getOrganization({ const currentOrg = await getOrganization({
orgId: String(localStorage.getItem("orgData.id")), orgId: String(localStorage.getItem('orgData.id'))
}); });
setCurrentOrg(currentOrg); setCurrentOrg(currentOrg);
})(); })();
}, []); }, []);
const closeApp = async () => { const closeApp = async () => {
console.log("Logging out..."); console.log('Logging out...');
await logout(); await logout();
router.push("/login"); router.push('/login');
}; };
return ( return (
@@ -163,7 +163,7 @@ export default function Navbar() {
</div> </div>
<div <div
onClick={() => onClick={() =>
router.push("/settings/personal/" + router.query.id) router.push('/settings/personal/' + router.query.id)
} }
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md" className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
> >
@@ -173,11 +173,11 @@ export default function Navbar() {
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-between w-full">
<div> <div>
<p className="text-gray-300 px-2 pt-1 text-sm"> <p className="text-gray-300 px-2 pt-1 text-sm">
{" "} {' '}
{user?.firstName} {user?.lastName} {user?.firstName} {user?.lastName}
</p> </p>
<p className="text-gray-400 px-2 pb-1 text-xs"> <p className="text-gray-400 px-2 pb-1 text-xs">
{" "} {' '}
{user?.email} {user?.email}
</p> </p>
</div> </div>
@@ -194,7 +194,7 @@ export default function Navbar() {
</div> </div>
<div <div
onClick={() => onClick={() =>
router.push("/settings/org/" + router.query.id) router.push('/settings/org/' + router.query.id)
} }
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md" className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
> >
@@ -217,7 +217,7 @@ export default function Navbar() {
> >
<div <div
onClick={() => onClick={() =>
router.push("/settings/billing/" + router.query.id) router.push('/settings/billing/' + router.query.id)
} }
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200" className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
> >
@@ -235,7 +235,7 @@ export default function Navbar() {
<div <div
onClick={() => onClick={() =>
router.push( router.push(
"/settings/org/" + router.query.id + "?invite" '/settings/org/' + router.query.id + '?invite'
) )
} }
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1" className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
@@ -255,13 +255,14 @@ export default function Navbar() {
<div className="flex flex-col items-start px-1 mt-3 mb-2"> <div className="flex flex-col items-start px-1 mt-3 mb-2">
{orgs {orgs
.filter( .filter(
(org : { _id: string }) => org._id != localStorage.getItem("orgData.id") (org: { _id: string }) =>
org._id != localStorage.getItem('orgData.id')
) )
.map((org : { _id: string; name: string; }) => ( .map((org: { _id: string; name: string }) => (
<div <div
key={guidGenerator()} key={guidGenerator()}
onClick={() => { onClick={() => {
localStorage.setItem("orgData.id", org._id); localStorage.setItem('orgData.id', org._id);
router.reload(); router.reload();
}} }}
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md" className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
@@ -286,8 +287,8 @@ export default function Navbar() {
onClick={closeApp} onClick={closeApp}
className={`${ className={`${
active active
? "bg-red font-semibold text-white" ? 'bg-red font-semibold text-white'
: "text-gray-400" : 'text-gray-400'
} group flex w-full items-center rounded-md px-2 py-2 text-sm`} } group flex w-full items-center rounded-md px-2 py-2 text-sm`}
> >
<div className="relative flex justify-start items-center cursor-pointer select-none"> <div className="relative flex justify-start items-center cursor-pointer select-none">
@@ -1,24 +0,0 @@
import token from "~/pages/api/auth/Token";
export default class SecurityClient {
static #token = "";
constructor() {}
static setToken(token) {
this.#token = token;
}
static async fetchCall(resource, options) {
let req = new Request(resource, options);
if (this.#token == "") {
this.setToken(await token());
}
if (this.#token) {
req.headers.set("Authorization", "Bearer " + this.#token);
return fetch(req);
}
}
}
@@ -0,0 +1,27 @@
import token from '~/pages/api/auth/Token';
export default class SecurityClient {
static #token = '';
constructor() {}
static setToken(token: string) {
this.#token = token;
}
static async fetchCall(
resource: RequestInfo,
options?: RequestInit | undefined
) {
const req = new Request(resource, options);
if (this.#token == '') {
this.setToken(await token());
}
if (this.#token) {
req.headers.set('Authorization', 'Bearer ' + this.#token);
return fetch(req);
}
}
}
+39 -43
View File
@@ -1,17 +1,17 @@
import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm"; import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm';
import login1 from "~/pages/api/auth/Login1"; import login1 from '~/pages/api/auth/Login1';
import login2 from "~/pages/api/auth/Login2"; import login2 from '~/pages/api/auth/Login2';
import getOrganizations from "~/pages/api/organization/getOrgs"; import getOrganizations from '~/pages/api/organization/getOrgs';
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects"; import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects';
import pushKeys from "./secrets/pushKeys"; import pushKeys from './secrets/pushKeys';
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage"; import Telemetry from './telemetry/Telemetry';
import SecurityClient from "./SecurityClient"; import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
import Telemetry from "./telemetry/Telemetry"; import SecurityClient from './SecurityClient';
const nacl = require("tweetnacl"); const nacl = require('tweetnacl');
nacl.util = require("tweetnacl-util"); nacl.util = require('tweetnacl-util');
const jsrp = require("jsrp"); const jsrp = require('jsrp');
const client = new jsrp.client(); const client = new jsrp.client();
/** /**
@@ -37,7 +37,7 @@ const attemptLogin = async (
client.init( client.init(
{ {
username: email, username: email,
password: password, password: password
}, },
async () => { async () => {
const clientPublicKey = client.getPublicKey(); const clientPublicKey = client.getPublicKey();
@@ -54,54 +54,53 @@ const attemptLogin = async (
await login2(email, clientProof); await login2(email, clientProof);
SecurityClient.setToken(token); SecurityClient.setToken(token);
const privateKey = Aes256Gcm.decrypt( const privateKey = Aes256Gcm.decrypt({
encryptedPrivateKey, ciphertext: encryptedPrivateKey,
iv, iv,
tag, tag,
password secret: password
.slice(0, 32) .slice(0, 32)
.padStart( .padStart(
32 + (password.slice(0, 32).length - new Blob([password]).size), 32 + (password.slice(0, 32).length - new Blob([password]).size),
"0" '0'
) )
); });
saveTokenToLocalStorage({ saveTokenToLocalStorage({
token,
publicKey, publicKey,
encryptedPrivateKey, encryptedPrivateKey,
iv, iv,
tag, tag,
privateKey, privateKey
}); });
const userOrgs = await getOrganizations(); const userOrgs = await getOrganizations();
const userOrgsData = userOrgs.map((org) => org._id); const userOrgsData = userOrgs.map((org) => org._id);
let orgToLogin; let orgToLogin;
if (userOrgsData.includes(localStorage.getItem("orgData.id"))) { if (userOrgsData.includes(localStorage.getItem('orgData.id'))) {
orgToLogin = localStorage.getItem("orgData.id"); orgToLogin = localStorage.getItem('orgData.id');
} else { } else {
orgToLogin = userOrgsData[0]; orgToLogin = userOrgsData[0];
localStorage.setItem("orgData.id", orgToLogin); localStorage.setItem('orgData.id', orgToLogin);
} }
let orgUserProjects = await getOrganizationUserProjects({ let orgUserProjects = await getOrganizationUserProjects({
orgId: orgToLogin, orgId: orgToLogin
}); });
orgUserProjects = orgUserProjects?.map((project) => project._id); orgUserProjects = orgUserProjects?.map((project) => project._id);
let projectToLogin; let projectToLogin;
if ( if (
orgUserProjects.includes(localStorage.getItem("projectData.id")) orgUserProjects.includes(localStorage.getItem('projectData.id'))
) { ) {
projectToLogin = localStorage.getItem("projectData.id"); projectToLogin = localStorage.getItem('projectData.id');
} else { } else {
try { try {
projectToLogin = orgUserProjects[0]; projectToLogin = orgUserProjects[0];
localStorage.setItem("projectData.id", projectToLogin); localStorage.setItem('projectData.id', projectToLogin);
} catch (error) { } catch (error) {
console.log("ERROR: User likely has no projects. ", error); console.log('ERROR: User likely has no projects. ', error);
} }
} }
@@ -110,38 +109,35 @@ const attemptLogin = async (
await pushKeys({ await pushKeys({
obj: { obj: {
DATABASE_URL: [ DATABASE_URL: [
"mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net", 'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
"personal", 'personal'
], ],
DB_USERNAME: ["user1234", "personal"], DB_USERNAME: ['user1234', 'personal'],
DB_PASSWORD: ["ah8jak3hk8dhiu4dw7whxwe1l", "personal"], DB_PASSWORD: ['example_password', 'personal'],
TWILIO_AUTH_TOKEN: [ TWILIO_AUTH_TOKEN: ['example_twillion_token', 'shared'],
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG", WEBSITE_URL: ['http://localhost:3000', 'shared'],
"shared", STRIPE_SECRET_KEY: ['sk_test_7348oyho4hfq398HIUOH78', 'shared']
],
WEBSITE_URL: ["http://localhost:3000", "shared"],
STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"],
}, },
workspaceId: projectToLogin, workspaceId: projectToLogin,
env: "Development", env: 'Development'
}); });
} }
if (email) { if (email) {
telemetry.identify(email); telemetry.identify(email);
telemetry.capture("User Logged In"); telemetry.capture('User Logged In');
} }
if (isLogin) { if (isLogin) {
router.push("/dashboard/"); router.push('/dashboard/');
} }
} catch (error) { } catch (error) {
setErrorLogin(true); setErrorLogin(true);
console.log("Login response not available"); console.log('Login response not available');
} }
} }
); );
} catch (error) { } catch (error) {
console.log("Something went wrong during authentication"); console.log('Something went wrong during authentication');
} }
return true; return true;
}; };
@@ -0,0 +1,71 @@
import getOrganizationUsers from '~/pages/api/organization/GetOrgUsers';
import checkUserAction from '~/pages/api/userActions/checkUserAction';
interface OnboardingCheckProps {
setTotalOnboardingActionsDone?: (value: number) => void;
setHasUserClickedSlack?: (value: boolean) => void;
setHasUserClickedIntro?: (value: boolean) => void;
setHasUserStarred?: (value: boolean) => void;
setHasUserPushedSecrets?: (value: boolean) => void;
setUsersInOrg?: (value: boolean) => void;
}
/**
* This function checks which onboarding steps a user has already finished.
*/
const onboardingCheck = async ({
setTotalOnboardingActionsDone,
setHasUserClickedSlack,
setHasUserClickedIntro,
setHasUserStarred,
setHasUserPushedSecrets,
setUsersInOrg
}: OnboardingCheckProps) => {
let countActions = 0;
const userActionSlack = await checkUserAction({
action: 'slack_cta_clicked'
});
if (userActionSlack) {
countActions = countActions + 1;
}
setHasUserClickedSlack &&
setHasUserClickedSlack(userActionSlack ? true : false);
const userActionSecrets = await checkUserAction({
action: 'first_time_secrets_pushed'
});
if (userActionSecrets) {
countActions = countActions + 1;
}
setHasUserPushedSecrets &&
setHasUserPushedSecrets(userActionSecrets ? true : false);
const userActionIntro = await checkUserAction({
action: 'intro_cta_clicked'
});
if (userActionIntro) {
countActions = countActions + 1;
}
setHasUserClickedIntro &&
setHasUserClickedIntro(userActionIntro ? true : false);
const userActionStar = await checkUserAction({
action: 'star_cta_clicked'
});
if (userActionStar) {
countActions = countActions + 1;
}
setHasUserStarred && setHasUserStarred(userActionStar ? true : false);
const orgId = localStorage.getItem('orgData.id');
const orgUsers = await getOrganizationUsers({
orgId: orgId ? orgId : ''
});
if (orgUsers.length > 1) {
countActions = countActions + 1;
}
setUsersInOrg && setUsersInOrg(orgUsers.length > 1);
setTotalOnboardingActionsDone && setTotalOnboardingActionsDone(countActions);
};
export default onboardingCheck;
@@ -1,63 +0,0 @@
/**
* @fileoverview Provides easy encryption/decryption methods using AES 256 GCM.
*/
"use strict";
const crypto = require("crypto");
const ALGORITHM = "aes-256-gcm";
const BLOCK_SIZE_BYTES = 16; // 128 bit
/**
* Provides easy encryption/decryption methods using AES 256 GCM.
*/
class Aes256Gcm {
/**
* No need to run the constructor. The class only has static methods.
*/
constructor() {}
/**
* Encrypts text with AES 256 GCM.
* @param {string} text - Cleartext to encode.
* @param {string} secret - Shared secret key, must be 32 bytes.
* @returns {object}
*/
static encrypt(text, secret) {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, "utf8", "base64");
ciphertext += cipher.final("base64");
return {
ciphertext,
iv: iv.toString("base64"),
tag: cipher.getAuthTag().toString("base64"),
};
}
/**
* Decrypts AES 256 CGM encrypted text.
* @param {string} ciphertext - Base64-encoded ciphertext.
* @param {string} iv - The base64-encoded initialization vector.
* @param {string} tag - The base64-encoded authentication tag generated by getAuthTag().
* @param {string} secret - Shared secret key, must be 32 bytes.
* @returns {string}
*/
static decrypt(ciphertext, iv, tag, secret) {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, "base64")
);
decipher.setAuthTag(Buffer.from(tag, "base64"));
let cleartext = decipher.update(ciphertext, "base64", "utf8");
cleartext += decipher.final("utf8");
return cleartext;
}
}
module.exports = Aes256Gcm;
@@ -0,0 +1,82 @@
/**
* @fileoverview Provides easy encryption/decryption methods using AES 256 GCM.
*/
import crypto from 'crypto';
const ALGORITHM = 'aes-256-gcm';
const BLOCK_SIZE_BYTES = 16; // 128 bit
interface EncryptProps {
text: string;
secret: string;
}
interface DecryptProps {
ciphertext: string;
iv: string;
tag: string;
secret: string;
}
interface EncryptOutputProps {
ciphertext: string;
iv: string;
tag: string;
}
/**
* Provides easy encryption/decryption methods using AES 256 GCM.
*/
class Aes256Gcm {
/**
* No need to run the constructor. The class only has static methods.
*/
constructor() {}
/**
* Encrypts text with AES 256 GCM.
* @param {object} obj
* @param {string} obj.text - Cleartext to encode.
* @param {string} obj.secret - Shared secret key, must be 32 bytes.
* @returns {object}
*/
// { ciphertext: string; iv: string; tag: string; }
static encrypt({ text, secret }: EncryptProps): EncryptOutputProps {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
ciphertext,
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64')
};
}
/**
* Decrypts AES 256 CGM encrypted text.
* @param {object} obj
* @param {string} obj.ciphertext - Base64-encoded ciphertext.
* @param {string} obj.iv - The base64-encoded initialization vector.
* @param {string} obj.tag - The base64-encoded authentication tag generated by getAuthTag().
* @param {string} obj.secret - Shared secret key, must be 32 bytes.
* @returns {string}
*/
static decrypt({ ciphertext, iv, tag, secret }: DecryptProps): string {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, 'base64')
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
return cleartext;
}
}
export default Aes256Gcm;
@@ -1,11 +1,11 @@
import changePassword2 from "~/pages/api/auth/ChangePassword2"; import changePassword2 from '~/pages/api/auth/ChangePassword2';
import SRP1 from "~/pages/api/auth/SRP1"; import SRP1 from '~/pages/api/auth/SRP1';
import Aes256Gcm from "./aes-256-gcm"; import Aes256Gcm from './aes-256-gcm';
const nacl = require("tweetnacl"); const nacl = require('tweetnacl');
nacl.util = require("tweetnacl-util"); nacl.util = require('tweetnacl-util');
const jsrp = require("jsrp"); const jsrp = require('jsrp');
const clientOldPassword = new jsrp.client(); const clientOldPassword = new jsrp.client();
const clientNewPassword = new jsrp.client(); const clientNewPassword = new jsrp.client();
@@ -34,7 +34,7 @@ const changePassword = async (
clientOldPassword.init( clientOldPassword.init(
{ {
username: email, username: email,
password: currentPassword, password: currentPassword
}, },
async () => { async () => {
const clientPublicKey = clientOldPassword.getPublicKey(); const clientPublicKey = clientOldPassword.getPublicKey();
@@ -42,13 +42,13 @@ const changePassword = async (
let serverPublicKey, salt; let serverPublicKey, salt;
try { try {
const res = await SRP1({ const res = await SRP1({
clientPublicKey: clientPublicKey, clientPublicKey: clientPublicKey
}); });
serverPublicKey = res.serverPublicKey; serverPublicKey = res.serverPublicKey;
salt = res.salt; salt = res.salt;
} catch (err) { } catch (err) {
setCurrentPasswordError(true); setCurrentPasswordError(true);
console.log("Wrong current password", err, 1); console.log('Wrong current password', err, 1);
} }
clientOldPassword.setSalt(salt); clientOldPassword.setSalt(salt);
@@ -58,27 +58,27 @@ const changePassword = async (
clientNewPassword.init( clientNewPassword.init(
{ {
username: email, username: email,
password: newPassword, password: newPassword
}, },
async () => { async () => {
clientNewPassword.createVerifier(async (err, result) => { clientNewPassword.createVerifier(async (err, result) => {
// The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø) // The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø)
let { ciphertext, iv, tag } = Aes256Gcm.encrypt( const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
localStorage.getItem("PRIVATE_KEY"), text: localStorage.getItem('PRIVATE_KEY'),
newPassword secret: newPassword
.slice(0, 32) .slice(0, 32)
.padStart( .padStart(
32 + 32 +
(newPassword.slice(0, 32).length - (newPassword.slice(0, 32).length -
new Blob([newPassword]).size), new Blob([newPassword]).size),
"0" '0'
) )
); });
if (ciphertext) { if (ciphertext) {
localStorage.setItem("encryptedPrivateKey", ciphertext); localStorage.setItem('encryptedPrivateKey', ciphertext);
localStorage.setItem("iv", iv); localStorage.setItem('iv', iv);
localStorage.setItem("tag", tag); localStorage.setItem('tag', tag);
let res; let res;
try { try {
@@ -88,14 +88,14 @@ const changePassword = async (
tag, tag,
salt: result.salt, salt: result.salt,
verifier: result.verifier, verifier: result.verifier,
clientProof, clientProof
}); });
if (res.status == 400) { if (res.status == 400) {
setCurrentPasswordError(true); setCurrentPasswordError(true);
} else if (res.status == 200) { } else if (res.status == 200) {
setPasswordChanged(true); setPasswordChanged(true);
setCurrentPassword(""); setCurrentPassword('');
setNewPassword(""); setNewPassword('');
} }
} catch (err) { } catch (err) {
setCurrentPasswordError(true); setCurrentPasswordError(true);
@@ -108,7 +108,7 @@ const changePassword = async (
} }
); );
} catch (error) { } catch (error) {
console.log("Something went wrong during changing the password"); console.log('Something went wrong during changing the password');
} }
return true; return true;
}; };
@@ -1,12 +1,12 @@
const nacl = require("tweetnacl"); const nacl = require('tweetnacl');
nacl.util = require("tweetnacl-util"); nacl.util = require('tweetnacl-util');
const aes = require("./aes-256-gcm"); import aes from './aes-256-gcm';
type encryptAsymmetricProps = { type encryptAsymmetricProps = {
plaintext: string; plaintext: string;
publicKey: string; publicKey: string;
privateKey: string; privateKey: string;
} };
/** /**
* Return assymmetrically encrypted [plaintext] using [publicKey] where * Return assymmetrically encrypted [plaintext] using [publicKey] where
@@ -19,7 +19,11 @@ type encryptAsymmetricProps = {
* @returns {String} ciphertext - base64-encoded ciphertext * @returns {String} ciphertext - base64-encoded ciphertext
* @returns {String} nonce - base64-encoded nonce * @returns {String} nonce - base64-encoded nonce
*/ */
const encryptAssymmetric = ({ plaintext, publicKey, privateKey }: encryptAsymmetricProps): object => { const encryptAssymmetric = ({
plaintext,
publicKey,
privateKey
}: encryptAsymmetricProps): object => {
const nonce = nacl.randomBytes(24); const nonce = nacl.randomBytes(24);
const ciphertext = nacl.box( const ciphertext = nacl.box(
nacl.util.decodeUTF8(plaintext), nacl.util.decodeUTF8(plaintext),
@@ -30,7 +34,7 @@ const encryptAssymmetric = ({ plaintext, publicKey, privateKey }: encryptAsymmet
return { return {
ciphertext: nacl.util.encodeBase64(ciphertext), ciphertext: nacl.util.encodeBase64(ciphertext),
nonce: nacl.util.encodeBase64(nonce), nonce: nacl.util.encodeBase64(nonce)
}; };
}; };
@@ -39,7 +43,7 @@ type decryptAsymmetricProps = {
nonce: string; nonce: string;
publicKey: string; publicKey: string;
privateKey: string; privateKey: string;
} };
/** /**
* Return assymmetrically decrypted [ciphertext] using [privateKey] where * Return assymmetrically decrypted [ciphertext] using [privateKey] where
@@ -49,9 +53,13 @@ type decryptAsymmetricProps = {
* @param {String} obj.nonce - nonce * @param {String} obj.nonce - nonce
* @param {String} obj.publicKey - base64-encoded public key of the sender * @param {String} obj.publicKey - base64-encoded public key of the sender
* @param {String} obj.privateKey - base64-encoded private key of the receiver (current user) * @param {String} obj.privateKey - base64-encoded private key of the receiver (current user)
* @param {String} plaintext - UTF8 plaintext
*/ */
const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }: decryptAsymmetricProps): string => { const decryptAssymmetric = ({
ciphertext,
nonce,
publicKey,
privateKey
}: decryptAsymmetricProps): string => {
const plaintext = nacl.box.open( const plaintext = nacl.box.open(
nacl.util.decodeBase64(ciphertext), nacl.util.decodeBase64(ciphertext),
nacl.util.decodeBase64(nonce), nacl.util.decodeBase64(nonce),
@@ -65,7 +73,7 @@ const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }: decryp
type encryptSymmetricProps = { type encryptSymmetricProps = {
plaintext: string; plaintext: string;
key: string; key: string;
} };
/** /**
* Return symmetrically encrypted [plaintext] using [key]. * Return symmetrically encrypted [plaintext] using [key].
@@ -73,15 +81,18 @@ type encryptSymmetricProps = {
* @param {String} obj.plaintext - plaintext to encrypt * @param {String} obj.plaintext - plaintext to encrypt
* @param {String} obj.key - 16-byte hex key * @param {String} obj.key - 16-byte hex key
*/ */
const encryptSymmetric = ({ plaintext, key }: encryptSymmetricProps): object => { const encryptSymmetric = ({
plaintext,
key
}: encryptSymmetricProps): object => {
let ciphertext, iv, tag; let ciphertext, iv, tag;
try { try {
const obj = aes.encrypt(plaintext, key); const obj = aes.encrypt({ text: plaintext, secret: key });
ciphertext = obj.ciphertext; ciphertext = obj.ciphertext;
iv = obj.iv; iv = obj.iv;
tag = obj.tag; tag = obj.tag;
} catch (err) { } catch (err) {
console.log("Failed to perform encryption"); console.log('Failed to perform encryption');
console.log(err); console.log(err);
process.exit(1); process.exit(1);
} }
@@ -89,7 +100,7 @@ const encryptSymmetric = ({ plaintext, key }: encryptSymmetricProps): object =>
return { return {
ciphertext, ciphertext,
iv, iv,
tag, tag
}; };
}; };
@@ -98,7 +109,7 @@ type decryptSymmetricProps = {
iv: string; iv: string;
tag: string; tag: string;
key: string; key: string;
} };
/** /**
* Return symmetrically decypted [ciphertext] using [iv], [tag], * Return symmetrically decypted [ciphertext] using [iv], [tag],
@@ -110,12 +121,17 @@ type decryptSymmetricProps = {
* @param {String} obj.key - 32-byte hex key * @param {String} obj.key - 32-byte hex key
* *
*/ */
const decryptSymmetric = ({ ciphertext, iv, tag, key }: decryptSymmetricProps): string => { const decryptSymmetric = ({
ciphertext,
iv,
tag,
key
}: decryptSymmetricProps): string => {
let plaintext; let plaintext;
try { try {
plaintext = aes.decrypt(ciphertext, iv, tag, key); plaintext = aes.decrypt({ ciphertext, iv, tag, secret: key });
} catch (err) { } catch (err) {
console.log("Failed to perform decryption"); console.log('Failed to perform decryption');
process.exit(1); process.exit(1);
} }
@@ -126,5 +142,5 @@ export {
decryptAssymmetric, decryptAssymmetric,
decryptSymmetric, decryptSymmetric,
encryptAssymmetric, encryptAssymmetric,
encryptSymmetric, encryptSymmetric
}; };
@@ -1,98 +0,0 @@
import issueBackupPrivateKey from "~/pages/api/auth/IssueBackupPrivateKey";
import SRP1 from "~/pages/api/auth/SRP1";
import generateBackupPDF from "../generateBackupPDF";
import Aes256Gcm from "./aes-256-gcm";
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const jsrp = require("jsrp");
const clientPassword = new jsrp.client();
const clientKey = new jsrp.client();
const crypto = require("crypto");
/**
* This function loggs in the user (whether it's right after signup, or a normal login)
* @param {*} email
* @param {*} password
* @param {*} setErrorLogin
* @param {*} router
* @param {*} isSignUp
* @returns
*/
const issueBackupKey = async ({
email,
password,
personalName,
setBackupKeyError,
setBackupKeyIssued,
}) => {
try {
setBackupKeyError(false);
setBackupKeyIssued(false);
clientPassword.init(
{
username: email,
password: password,
},
async () => {
const clientPublicKey = clientPassword.getPublicKey();
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey,
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setBackupKeyError(true);
console.log("Wrong current password", err, 1);
}
clientPassword.setSalt(salt);
clientPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientPassword.getProof(); // called M1
const generatedKey = crypto.randomBytes(16).toString("hex");
clientKey.init(
{
username: email,
password: generatedKey,
},
async () => {
clientKey.createVerifier(async (err, result) => {
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
generatedKey
);
const res = await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof,
});
if (res.status == 400) {
setBackupKeyError(true);
} else if (res.status == 200) {
generateBackupPDF(personalName, email, generatedKey);
setBackupKeyIssued(true);
}
});
}
);
}
);
} catch (error) {
setBackupKeyError(true);
console.log("Failed to issue a backup key");
}
return true;
};
export default issueBackupKey;
@@ -0,0 +1,113 @@
import issueBackupPrivateKey from '~/pages/api/auth/IssueBackupPrivateKey';
import SRP1 from '~/pages/api/auth/SRP1';
import generateBackupPDF from '../generateBackupPDF';
import Aes256Gcm from './aes-256-gcm';
const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');
const jsrp = require('jsrp');
const clientPassword = new jsrp.client();
const clientKey = new jsrp.client();
const crypto = require('crypto');
interface BackupKeyProps {
email: string;
password: string;
personalName: string;
setBackupKeyError: (value: boolean) => void;
setBackupKeyIssued: (value: boolean) => void;
}
/**
* This function issue a backup key for a user
* @param {obkect} obj
* @param {string} obj.email - email of a user issuing a backup key
* @param {string} obj.password - password of a user issuing a backup key
* @param {string} obj.personalName - name of a user issuing a backup key
* @param {function} obj.setBackupKeyError - state function that turns true if there is an erorr with a backup key
* @param {function} obj.setBackupKeyIssued - state function that turns true if a backup key was issued correctly
* @returns
*/
const issueBackupKey = async ({
email,
password,
personalName,
setBackupKeyError,
setBackupKeyIssued
}: BackupKeyProps) => {
try {
setBackupKeyError(false);
setBackupKeyIssued(false);
clientPassword.init(
{
username: email,
password: password
},
async () => {
const clientPublicKey = clientPassword.getPublicKey();
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setBackupKeyError(true);
console.log('Wrong current password', err, 1);
}
clientPassword.setSalt(salt);
clientPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientPassword.getProof(); // called M1
const generatedKey = crypto.randomBytes(16).toString('hex');
clientKey.init(
{
username: email,
password: generatedKey
},
async () => {
clientKey.createVerifier(
async (err: any, result: { salt: string; verifier: string }) => {
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
text: String(localStorage.getItem('PRIVATE_KEY')),
secret: generatedKey
});
const res = await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof
});
if (res?.status == 400) {
setBackupKeyError(true);
} else if (res?.status == 200) {
generateBackupPDF({
personalName,
personalEmail: email,
generatedKey
});
setBackupKeyIssued(true);
}
}
);
}
);
}
);
} catch (error) {
setBackupKeyError(true);
console.log('Failed to issue a backup key');
}
return true;
};
export default issueBackupKey;
@@ -6,21 +6,21 @@ const LINE =
* @param {Buffer} src - source buffer * @param {Buffer} src - source buffer
* @returns {String} text - text of buffer * @returns {String} text - text of buffer
*/ */
function parse(src) { function parse(src: Buffer) {
const obj = {}; const obj: Record<string, string> = {};
// Convert buffer to string // Convert buffer to string
let lines = src.toString(); let lines = src.toString();
// Convert line breaks to same format // Convert line breaks to same format
lines = lines.replace(/\r\n?/gm, "\n"); lines = lines.replace(/\r\n?/gm, '\n');
let match; let match;
while ((match = LINE.exec(lines)) != null) { while ((match = LINE.exec(lines)) != null) {
const key = match[1]; const key = match[1];
// Default undefined or null to empty string // Default undefined or null to empty string
let value = match[2] || ""; let value = match[2] || '';
// Remove whitespace // Remove whitespace
value = value.trim(); value = value.trim();
@@ -29,12 +29,12 @@ function parse(src) {
const maybeQuote = value[0]; const maybeQuote = value[0];
// Remove surrounding quotes // Remove surrounding quotes
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2"); value = value.replace(/^(['"`])([\s\S]*)\1$/gm, '$2');
// Expand newlines if double quoted // Expand newlines if double quoted
if (maybeQuote === '"') { if (maybeQuote === '"') {
value = value.replace(/\\n/g, "\n"); value = value.replace(/\\n/g, '\n');
value = value.replace(/\\r/g, "\r"); value = value.replace(/\\r/g, '\r');
} }
// Add to object // Add to object
File diff suppressed because one or more lines are too long
@@ -3,19 +3,19 @@
* @returns * @returns
*/ */
const guidGenerator = () => { const guidGenerator = () => {
var S4 = function () { const S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}; };
return ( return (
S4() + S4() +
S4() + S4() +
"-" + '-' +
S4() + S4() +
"-" + '-' +
S4() + S4() +
"-" + '-' +
S4() + S4() +
"-" + '-' +
S4() + S4() +
S4() + S4() +
S4() S4()
@@ -1,22 +1,30 @@
import getSecrets from "~/pages/api/files/GetSecrets"; import getSecrets from '~/pages/api/files/GetSecrets';
import { envMapping } from "../../../public/data/frequentConstants"; import { envMapping } from '../../../public/data/frequentConstants';
import guidGenerator from "../randomId"; import guidGenerator from '../randomId';
const { const {
decryptAssymmetric, decryptAssymmetric,
decryptSymmetric, decryptSymmetric
} = require("../cryptography/crypto"); } = require('../cryptography/crypto');
const nacl = require("tweetnacl"); const nacl = require('tweetnacl');
nacl.util = require("tweetnacl-util"); nacl.util = require('tweetnacl-util');
interface Props {
env: keyof typeof envMapping;
setFileState: any;
setIsKeyAvailable: any;
setData: any;
workspaceId: string;
}
const getSecretsForProject = async ({ const getSecretsForProject = async ({
env, env,
setFileState, setFileState,
setIsKeyAvailable, setIsKeyAvailable,
setData, setData,
workspaceId, workspaceId
}) => { }: Props) => {
try { try {
let file; let file;
try { try {
@@ -24,44 +32,42 @@ const getSecretsForProject = async ({
setFileState(file); setFileState(file);
} catch (error) { } catch (error) {
console.log("ERROR: Not able to access the latest file"); console.log('ERROR: Not able to access the latest file');
} }
// This is called isKeyAvilable but what it really means is if a person is able to create new key pairs // This is called isKeyAvilable but what it really means is if a person is able to create new key pairs
setIsKeyAvailable( setIsKeyAvailable(!file.key ? file.secrets.length == 0 : true);
!file.key ? (file.secrets.length == 0 ? true : false) : true
);
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
let tempFileState = []; const tempFileState: { key: string; value: string; type: string }[] = [];
if (file.key) { if (file.key) {
// assymmetrically decrypt symmetric key with local private key // assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({ const key = decryptAssymmetric({
ciphertext: file.key.encryptedKey, ciphertext: file.key.encryptedKey,
nonce: file.key.nonce, nonce: file.key.nonce,
publicKey: file.key.sender.publicKey, publicKey: file.key.sender.publicKey,
privateKey: PRIVATE_KEY, privateKey: PRIVATE_KEY
}); });
file.secrets.map((secretPair) => { file.secrets.map((secretPair: any) => {
// decrypt .env file with symmetric key // decrypt .env file with symmetric key
const plainTextKey = decryptSymmetric({ const plainTextKey = decryptSymmetric({
ciphertext: secretPair.secretKey.ciphertext, ciphertext: secretPair.secretKey.ciphertext,
iv: secretPair.secretKey.iv, iv: secretPair.secretKey.iv,
tag: secretPair.secretKey.tag, tag: secretPair.secretKey.tag,
key, key
}); });
const plainTextValue = decryptSymmetric({ const plainTextValue = decryptSymmetric({
ciphertext: secretPair.secretValue.ciphertext, ciphertext: secretPair.secretValue.ciphertext,
iv: secretPair.secretValue.iv, iv: secretPair.secretValue.iv,
tag: secretPair.secretValue.tag, tag: secretPair.secretValue.tag,
key, key
}); });
tempFileState.push({ tempFileState.push({
key: plainTextKey, key: plainTextKey,
value: plainTextValue, value: plainTextValue,
type: secretPair.type, type: secretPair.type
}); });
}); });
} }
@@ -72,9 +78,9 @@ const getSecretsForProject = async ({
return { return {
id: guidGenerator(), id: guidGenerator(),
pos: index, pos: index,
key: line["key"], key: line['key'],
value: line["value"], value: line['value'],
type: line["type"] type: line['type']
} }
}) })
); );
@@ -82,12 +88,12 @@ const getSecretsForProject = async ({
return tempFileState.map((line, index) => [ return tempFileState.map((line, index) => [
guidGenerator(), guidGenerator(),
index, index,
line["key"], line['key'],
line["value"], line['value'],
line["type"], line['type']
]); ]);
} catch (error) { } catch (error) {
console.log("Something went wrong during accessing or decripting secrets."); console.log('Something went wrong during accessing or decripting secrets.');
} }
return true; return true;
}; };
@@ -1,74 +0,0 @@
import publicKeyInfical from "~/pages/api/auth/publicKeyInfisical";
import changeHerokuConfigVars from "~/pages/api/integrations/ChangeHerokuConfigVars";
const crypto = require("crypto");
const {
encryptSymmetric,
encryptAssymmetric,
} = require("../cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const pushKeysIntegration = async ({ obj, integrationId }) => {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let randomBytes = crypto.randomBytes(16).toString("hex");
const secrets = Object.keys(obj).map((key) => {
// encrypt key
const {
ciphertext: ciphertextKey,
iv: ivKey,
tag: tagKey,
} = encryptSymmetric({
plaintext: key,
key: randomBytes,
});
// encrypt value
const {
ciphertext: ciphertextValue,
iv: ivValue,
tag: tagValue,
} = encryptSymmetric({
plaintext: obj[key],
key: randomBytes,
});
const visibility = "shared";
return {
ciphertextKey,
ivKey,
tagKey,
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
ciphertextValue,
ivValue,
tagValue,
hashValue: crypto.createHash("sha256").update(obj[key]).digest("hex"),
type: visibility,
};
});
// obtain public keys of all receivers (i.e. members in workspace)
let publicKeyInfisical = await publicKeyInfical();
publicKeyInfisical = (await publicKeyInfisical.json()).publicKey;
// assymmetrically encrypt key with each receiver public keys
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: publicKeyInfisical,
privateKey: PRIVATE_KEY,
});
const key = {
encryptedKey: ciphertext,
nonce,
};
changeHerokuConfigVars({ integrationId, key, secrets });
};
export default pushKeysIntegration;
@@ -0,0 +1,79 @@
import publicKeyInfical from '~/pages/api/auth/publicKeyInfisical';
import changeHerokuConfigVars from '~/pages/api/integrations/ChangeHerokuConfigVars';
const crypto = require('crypto');
const {
encryptSymmetric,
encryptAssymmetric
} = require('../cryptography/crypto');
const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');
interface Props {
obj: Record<string, string>;
integrationId: string;
}
const pushKeysIntegration = async ({ obj, integrationId }: Props) => {
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
const randomBytes = crypto.randomBytes(16).toString('hex');
const secrets = Object.keys(obj).map((key) => {
// encrypt key
const {
ciphertext: ciphertextKey,
iv: ivKey,
tag: tagKey
} = encryptSymmetric({
plaintext: key,
key: randomBytes
});
// encrypt value
const {
ciphertext: ciphertextValue,
iv: ivValue,
tag: tagValue
} = encryptSymmetric({
plaintext: obj[key],
key: randomBytes
});
const visibility = 'shared';
return {
ciphertextKey,
ivKey,
tagKey,
hashKey: crypto.createHash('sha256').update(key).digest('hex'),
ciphertextValue,
ivValue,
tagValue,
hashValue: crypto.createHash('sha256').update(obj[key]).digest('hex'),
type: visibility
};
});
// obtain public keys of all receivers (i.e. members in workspace)
const publicKeyInfisical = await publicKeyInfical();
const publicKey = (await publicKeyInfisical.json()).publicKey;
// assymmetrically encrypt key with each receiver public keys
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey,
privateKey: PRIVATE_KEY
});
const key = {
encryptedKey: ciphertext,
nonce
};
changeHerokuConfigVars({ integrationId, key, secrets });
};
export default pushKeysIntegration;
+15 -15
View File
@@ -29,7 +29,7 @@
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"next": "^12.2.5", "next": "^12.2.5",
"posthog-js": "^1.34.0", "posthog-js": "^1.34.0",
"query-string": "^7.1.1", "query-string": "^7.1.3",
"react": "^17.0.2", "react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-code-input": "^3.10.1", "react-code-input": "^3.10.1",
@@ -2467,9 +2467,9 @@
} }
}, },
"node_modules/decode-uri-component": { "node_modules/decode-uri-component": {
"version": "0.2.0", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"
} }
@@ -5826,11 +5826,11 @@
} }
}, },
"node_modules/query-string": { "node_modules/query-string": {
"version": "7.1.1", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"dependencies": { "dependencies": {
"decode-uri-component": "^0.2.0", "decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0", "filter-obj": "^1.1.0",
"split-on-first": "^1.0.0", "split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0" "strict-uri-encode": "^2.0.0"
@@ -9126,9 +9126,9 @@
} }
}, },
"decode-uri-component": { "decode-uri-component": {
"version": "0.2.0", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==" "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
}, },
"deep-is": { "deep-is": {
"version": "0.1.4", "version": "0.1.4",
@@ -11477,11 +11477,11 @@
"dev": true "dev": true
}, },
"query-string": { "query-string": {
"version": "7.1.1", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"requires": { "requires": {
"decode-uri-component": "^0.2.0", "decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0", "filter-obj": "^1.1.0",
"split-on-first": "^1.0.0", "split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0" "strict-uri-encode": "^2.0.0"
+1 -1
View File
@@ -32,7 +32,7 @@
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"next": "^12.2.5", "next": "^12.2.5",
"posthog-js": "^1.34.0", "posthog-js": "^1.34.0",
"query-string": "^7.1.1", "query-string": "^7.1.3",
"react": "^17.0.2", "react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-code-input": "^3.10.1", "react-code-input": "^3.10.1",
@@ -1,4 +1,13 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
interface Props {
encryptedPrivateKey: string;
iv: string;
tag: string;
salt: string;
verifier: string;
clientProof: string;
}
/** /**
* This is the second step of the change password process (pake) * This is the second step of the change password process (pake)
@@ -11,12 +20,12 @@ const changePassword2 = ({
tag, tag,
salt, salt,
verifier, verifier,
clientProof, clientProof
}) => { }: Props) => {
return SecurityClient.fetchCall("/api/v1/password/change-password", { return SecurityClient.fetchCall('/api/v1/password/change-password', {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
clientProof: clientProof, clientProof: clientProof,
@@ -24,13 +33,13 @@ const changePassword2 = ({
iv: iv, iv: iv,
tag: tag, tag: tag,
salt: salt, salt: salt,
verifier: verifier, verifier: verifier
}), })
}).then(async (res) => { }).then(async (res) => {
if (res.status == 200) { if (res && res.status == 200) {
return res; return res;
} else { } else {
console.log("Failed to change the password"); console.log('Failed to change the password');
} }
}); });
}; };
-25
View File
@@ -1,25 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient.js";
/**
* This function is used to check if the user is authenticated.
* To do that, we get their tokens from cookies, and verify if they are good.
* @param {*} req
* @param {*} res
* @returns
*/
const checkAuth = async (req, res) => {
return SecurityClient.fetchCall("/api/v1/auth/checkAuth", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
}).then((res) => {
if (res.status == 200) {
return res;
} else {
console.log("Not authorized");
}
});
};
export default checkAuth;
+22
View File
@@ -0,0 +1,22 @@
import SecurityClient from '~/utilities/SecurityClient';
/**
* This function is used to check if the user is authenticated.
* To do that, we get their tokens from cookies, and verify if they are good.
*/
const checkAuth = async () => {
return SecurityClient.fetchCall('/api/v1/auth/checkAuth', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
}).then((res) => {
if (res && res.status == 200) {
return res;
} else {
console.log('Not authorized');
}
});
};
export default checkAuth;
@@ -1,20 +0,0 @@
/**
* This route check the verification code from the email that user just recieved
* @param {*} email
* @param {*} code
* @returns
*/
const checkEmailVerificationCode = (email, code) => {
return fetch("/api/v1/signup/email/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
code: code,
}),
});
};
export default checkEmailVerificationCode;
@@ -0,0 +1,26 @@
interface Props {
email: string;
code: string;
}
/**
* This route check the verification code from the email that user just recieved
* @param {object} obj
* @param {string} obj.email
* @param {string} obj.code
* @returns
*/
const checkEmailVerificationCode = ({ email, code }: Props) => {
return fetch('/api/v1/signup/email/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
code: code
})
});
};
export default checkEmailVerificationCode;
@@ -1,50 +0,0 @@
/**
* This function is called in the end of the signup process.
* It sends all the necessary nformation to the server.
* @param {*} email
* @param {*} firstName
* @param {*} lastName
* @param {*} workspace
* @param {*} publicKey
* @param {*} ciphertext
* @param {*} iv
* @param {*} tag
* @param {*} salt
* @param {*} verifier
* @returns
*/
const completeAccountInformationSignup = ({
email,
firstName,
lastName,
organizationName,
publicKey,
ciphertext,
iv,
tag,
salt,
verifier,
token,
}) => {
return fetch("/api/v1/signup/complete-account/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({
email,
firstName,
lastName,
publicKey,
encryptedPrivateKey: ciphertext,
organizationName,
iv,
tag,
salt,
verifier,
}),
});
};
export default completeAccountInformationSignup;
@@ -0,0 +1,66 @@
interface Props {
email: string;
firstName: string;
lastName: string;
publicKey: string;
ciphertext: string;
organizationName: string;
iv: string;
tag: string;
salt: string;
verifier: string;
token: string;
}
/**
* This function is called in the end of the signup process.
* It sends all the necessary nformation to the server.
* @param {object} obj
* @param {string} obj.email - email of the user completing signup
* @param {string} obj.firstName - first name of the user completing signup
* @param {string} obj.lastName - last name of the user completing sign up
* @param {string} obj.organizationName - organization name for this user (usually, [FIRST_NAME]'s organization)
* @param {string} obj.publicKey - public key of the user completing signup
* @param {string} obj.ciphertext
* @param {string} obj.iv
* @param {string} obj.tag
* @param {string} obj.salt
* @param {string} obj.verifier
* @param {string} obj.token - token that confirms a user's identity
* @returns
*/
const completeAccountInformationSignup = ({
email,
firstName,
lastName,
organizationName,
publicKey,
ciphertext,
iv,
tag,
salt,
verifier,
token
}: Props) => {
return fetch('/api/v1/signup/complete-account/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token
},
body: JSON.stringify({
email,
firstName,
lastName,
publicKey,
encryptedPrivateKey: ciphertext,
organizationName,
iv,
tag,
salt,
verifier
})
});
};
export default completeAccountInformationSignup;
@@ -1,47 +0,0 @@
/**
* This function is called in the end of the signup process.
* It sends all the necessary nformation to the server.
* @param {*} email
* @param {*} firstName
* @param {*} lastName
* @param {*} publicKey
* @param {*} ciphertext
* @param {*} iv
* @param {*} tag
* @param {*} salt
* @param {*} verifier
* @returns
*/
const completeAccountInformationSignupInvite = ({
email,
firstName,
lastName,
publicKey,
ciphertext,
iv,
tag,
salt,
verifier,
token,
}) => {
return fetch("/api/v1/signup/complete-account/invite", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({
email: email,
firstName: firstName,
lastName: lastName,
publicKey: publicKey,
encryptedPrivateKey: ciphertext,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier,
}),
});
};
export default completeAccountInformationSignupInvite;
@@ -0,0 +1,62 @@
interface Props {
email: string;
firstName: string;
lastName: string;
publicKey: string;
ciphertext: string;
iv: string;
tag: string;
salt: string;
verifier: string;
token: string;
}
/**
* This function is called in the end of the signup process.
* It sends all the necessary nformation to the server.
* @param {object} obj
* @param {string} obj.email - email of the user completing signupinvite flow
* @param {string} obj.firstName - first name of the user completing signupinvite flow
* @param {string} obj.lastName - last name of the user completing signupinvite flow
* @param {string} obj.publicKey - public key of the user completing signupinvite flow
* @param {string} obj.ciphertext
* @param {string} obj.iv
* @param {string} obj.tag
* @param {string} obj.salt
* @param {string} obj.verifier
* @param {string} obj.token - token that confirms a user's identity
* @returns
*/
const completeAccountInformationSignupInvite = ({
email,
firstName,
lastName,
publicKey,
ciphertext,
iv,
tag,
salt,
verifier,
token
}: Props) => {
return fetch('/api/v1/signup/complete-account/invite', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token
},
body: JSON.stringify({
email: email,
firstName: firstName,
lastName: lastName,
publicKey: publicKey,
encryptedPrivateKey: ciphertext,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier
})
});
};
export default completeAccountInformationSignupInvite;
@@ -1,40 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the route that issues a backup private key that will afterwards be added into a pdf
*/
const issueBackupPrivateKey = ({
encryptedPrivateKey,
iv,
tag,
salt,
verifier,
clientProof,
}) => {
return SecurityClient.fetchCall(
"/api/v1/password/backup-private-key",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
clientProof: clientProof,
encryptedPrivateKey: encryptedPrivateKey,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier,
}),
}
).then((res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to issue the backup key");
return res;
}
});
};
export default issueBackupPrivateKey;
@@ -0,0 +1,52 @@
import SecurityClient from '~/utilities/SecurityClient';
interface Props {
encryptedPrivateKey: string;
iv: string;
tag: string;
salt: string;
verifier: string;
clientProof: string;
}
/**
* This is the route that issues a backup private key that will afterwards be added into a pdf
* @param {object} obj
* @param {string} obj.encryptedPrivateKey
* @param {string} obj.iv
* @param {string} obj.tag
* @param {string} obj.salt
* @param {string} obj.verifier
* @param {string} obj.clientProof
* @returns
*/
const issueBackupPrivateKey = ({
encryptedPrivateKey,
iv,
tag,
salt,
verifier,
clientProof
}: Props) => {
return SecurityClient.fetchCall('/api/v1/password/backup-private-key', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
clientProof: clientProof,
encryptedPrivateKey: encryptedPrivateKey,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier
})
}).then((res) => {
if (res?.status !== 200) {
console.log('Failed to issue the backup key');
}
return res;
});
};
export default issueBackupPrivateKey;
+13 -13
View File
@@ -1,29 +1,29 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This route logs the user out. Note: the user should authorized to do this. * This route logs the user out. Note: the user should authorized to do this.
* We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry * We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry
*/ */
const logout = async () => { const logout = async () => {
return SecurityClient.fetchCall("/api/v1/auth/logout", { return SecurityClient.fetchCall('/api/v1/auth/logout', {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, },
credentials: "include", credentials: 'include'
}).then((res) => { }).then((res) => {
if (res?.status == 200) { if (res?.status == 200) {
SecurityClient.setToken(""); SecurityClient.setToken('');
// Delete the cookie by not setting a value; Alternatively clear the local storage // Delete the cookie by not setting a value; Alternatively clear the local storage
localStorage.setItem("publicKey", ""); localStorage.setItem('publicKey', '');
localStorage.setItem("encryptedPrivateKey", ""); localStorage.setItem('encryptedPrivateKey', '');
localStorage.setItem("iv", ""); localStorage.setItem('iv', '');
localStorage.setItem("tag", ""); localStorage.setItem('tag', '');
localStorage.setItem("PRIVATE_KEY", ""); localStorage.setItem('PRIVATE_KEY', '');
console.log("User logged out", res); console.log('User logged out', res);
return res; return res;
} else { } else {
console.log("Failed to log out"); console.log('Failed to log out');
} }
}); });
}; };
-26
View File
@@ -1,26 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the first step of the change password process (pake)
* @param {*} clientPublicKey
* @returns
*/
const SRP1 = ({ clientPublicKey }) => {
return SecurityClient.fetchCall("/api/v1/password/srp1", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
clientPublicKey,
}),
}).then(async (res) => {
if (res.status == 200) {
return await res.json();
} else {
console.log("Failed to do the first step of SRP");
}
});
};
export default SRP1;
+30
View File
@@ -0,0 +1,30 @@
import SecurityClient from '~/utilities/SecurityClient';
interface Props {
clientPublicKey: string;
}
/**
* This is the first step of the change password process (pake)
* @param {string} clientPublicKey
* @returns
*/
const SRP1 = ({ clientPublicKey }: Props) => {
return SecurityClient.fetchCall('/api/v1/password/srp1', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
clientPublicKey
})
}).then(async (res) => {
if (res && res.status == 200) {
return await res.json();
} else {
console.log('Failed to do the first step of SRP');
}
});
};
export default SRP1;
@@ -2,15 +2,15 @@
* This route send the verification email to the user's email (contains a 6-digit verification code) * This route send the verification email to the user's email (contains a 6-digit verification code)
* @param {*} email * @param {*} email
*/ */
const sendVerificationEmail = (email) => { const sendVerificationEmail = (email: string) => {
fetch("/api/v1/signup/email/signup", { fetch('/api/v1/signup/email/signup', {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
email: email, email: email
}), })
}); });
}; };
-17
View File
@@ -1,17 +0,0 @@
const token = async (req, res) => {
return fetch("/api/v1/auth/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).token;
} else {
console.log("Getting a new token failed");
}
});
};
export default token;
+17
View File
@@ -0,0 +1,17 @@
const token = async () => {
return fetch('/api/v1/auth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).token;
} else {
console.log('Getting a new token failed');
}
});
};
export default token;
@@ -1,20 +0,0 @@
/**
* This route verifies the signup invite link
* @param {*} email
* @param {*} code
* @returns
*/
const verifySignupInvite = ({ email, code }) => {
return fetch("/api/v1/invite-org/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
code,
}),
});
};
export default verifySignupInvite;
@@ -0,0 +1,26 @@
interface Props {
email: string;
code: string;
}
/**
* This route verifies the signup invite link
* @param {object} obj
* @param {string} obj.email - email that a user is trying to verify
* @param {string} obj.code - code that a user received to the abovementioned email
* @returns
*/
const verifySignupInvite = ({ email, code }: Props) => {
return fetch('/api/v1/invite-org/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
code
})
});
};
export default verifySignupInvite;
@@ -1,16 +0,0 @@
/**
* This route lets us get the public key of infisical. Th euser doesn't have to be authenticated since this is just the public key.
* @param {*} req
* @param {*} res
* @returns
*/
const publicKeyInfisical = (req, res) => {
return fetch("/api/v1/key/publicKey/infisical", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
};
export default publicKeyInfisical;
@@ -0,0 +1,10 @@
const publicKeyInfisical = () => {
return fetch('/api/v1/key/publicKey/infisical', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
};
export default publicKeyInfisical;
-33
View File
@@ -1,33 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient.js";
/**
* This function fetches the encrypted secrets from the .env file
* @param {*} workspaceId
* @param {*} env
* @returns
*/
const getSecrets = async (workspaceId, env) => {
return SecurityClient.fetchCall(
"/api/v1/secret/" +
workspaceId +
"?" +
new URLSearchParams({
environment: env,
channel: "web",
}),
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return await res.json();
} else {
console.log("Failed to get project secrets");
}
});
};
export default getSecrets;
+33
View File
@@ -0,0 +1,33 @@
import SecurityClient from '~/utilities/SecurityClient';
/**
* This function fetches the encrypted secrets from the .env file
* @param {string} workspaceId - project is for which a user is trying to get secrets
* @param {string} env - environment of a project for which a user is trying ot get secrets
* @returns
*/
const getSecrets = async (workspaceId: string, env: string) => {
return SecurityClient.fetchCall(
'/api/v1/secret/' +
workspaceId +
'?' +
new URLSearchParams({
environment: env,
channel: 'web'
}),
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
).then(async (res) => {
if (res && res.status == 200) {
return await res.json();
} else {
console.log('Failed to get project secrets');
}
});
};
export default getSecrets;
-30
View File
@@ -1,30 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function uploads the encrypted .env file
* @param {*} req
* @param {*} res
* @returns
*/
const uploadSecrets = async ({ workspaceId, secrets, keys, environment }) => {
return SecurityClient.fetchCall("/api/v1/secret/" + workspaceId, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
secrets,
keys,
environment,
channel: "web",
}),
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to push secrets");
}
});
};
export default uploadSecrets;
+45
View File
@@ -0,0 +1,45 @@
import SecurityClient from '~/utilities/SecurityClient';
interface Props {
workspaceId: string;
secrets: any;
keys: string;
environment: string;
}
/**
* This function uploads the encrypted .env file
* @param {object} obj
* @param {string} obj.workspaceId
* @param {} obj.secrets
* @param {} obj.keys
* @param {string} obj.environment
* @returns
*/
const uploadSecrets = async ({
workspaceId,
secrets,
keys,
environment
}: Props) => {
return SecurityClient.fetchCall('/api/v1/secret/' + workspaceId, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
secrets,
keys,
environment,
channel: 'web'
})
}).then(async (res) => {
if (res && res.status == 200) {
return res;
} else {
console.log('Failed to push secrets');
}
});
};
export default uploadSecrets;
@@ -1,25 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
const changeHerokuConfigVars = ({ integrationId, key, secrets }) => {
return SecurityClient.fetchCall(
"/api/v1/integration/" + integrationId + "/sync",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
key,
secrets,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to sync secrets to Heroku");
}
});
};
export default changeHerokuConfigVars;
@@ -0,0 +1,41 @@
import SecurityClient from '~/utilities/SecurityClient';
interface Props {
integrationId: string;
key: { encryptedKey: any; nonce: any };
secrets: {
ciphertextKey: any;
ivKey: any;
tagKey: any;
hashKey: any;
ciphertextValue: any;
ivValue: any;
tagValue: any;
hashValue: any;
type: string;
}[];
}
const changeHerokuConfigVars = ({ integrationId, key, secrets }: Props) => {
return SecurityClient.fetchCall(
'/api/v1/integration/' + integrationId + '/sync',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
key,
secrets
})
}
).then(async (res) => {
if (res && res.status == 200) {
return res;
} else {
console.log('Failed to sync secrets to Heroku');
}
});
};
export default changeHerokuConfigVars;
@@ -1,26 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route deletes an integration from a certain project
* @param {*} integrationId
* @returns
*/
const deleteIntegration = ({ integrationId }) => {
return SecurityClient.fetchCall(
"/api/v1/integration/" + integrationId,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspace;
} else {
console.log("Failed to delete an integration");
}
});
};
export default deleteIntegration;
@@ -0,0 +1,27 @@
import SecurityClient from '~/utilities/SecurityClient';
interface Props {
integrationId: string;
}
/**
* This route deletes an integration from a certain project
* @param {*} integrationId
* @returns
*/
const deleteIntegration = ({ integrationId }: Props) => {
return SecurityClient.fetchCall('/api/v1/integration/' + integrationId, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
}).then(async (res) => {
if (res && res.status == 200) {
return (await res.json()).workspace;
} else {
console.log('Failed to delete an integration');
}
});
};
export default deleteIntegration;
@@ -1,26 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route deletes an integration authorization from a certain project
* @param {*} integrationAuthId
* @returns
*/
const deleteIntegrationAuth = ({ integrationAuthId }) => {
return SecurityClient.fetchCall(
"/api/v1/integration-auth/" + integrationAuthId,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to delete an integration authorization");
}
});
};
export default deleteIntegrationAuth;
@@ -0,0 +1,30 @@
import SecurityClient from '~/utilities/SecurityClient';
interface Props {
integrationAuthId: string;
}
/**
* This route deletes an integration authorization from a certain project
* @param {*} integrationAuthId
* @returns
*/
const deleteIntegrationAuth = ({ integrationAuthId }: Props) => {
return SecurityClient.fetchCall(
'/api/v1/integration-auth/' + integrationAuthId,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
}
).then(async (res) => {
if (res && res.status == 200) {
return res;
} else {
console.log('Failed to delete an integration authorization');
}
});
};
export default deleteIntegrationAuth;
@@ -1,21 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
const getIntegrationApps = ({ integrationAuthId }) => {
return SecurityClient.fetchCall(
"/api/v1/integration-auth/" + integrationAuthId + "/apps",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).apps;
} else {
console.log("Failed to get available apps for an integration");
}
});
};
export default getIntegrationApps;
@@ -0,0 +1,25 @@
import SecurityClient from '~/utilities/SecurityClient';
interface Props {
integrationAuthId: string;
}
const getIntegrationApps = ({ integrationAuthId }: Props) => {
return SecurityClient.fetchCall(
'/api/v1/integration-auth/' + integrationAuthId + '/apps',
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
).then(async (res) => {
if (res && res.status == 200) {
return (await res.json()).apps;
} else {
console.log('Failed to get available apps for an integration');
}
});
};
export default getIntegrationApps;
@@ -1,18 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
const getIntegrations = () => {
return SecurityClient.fetchCall("/api/v1/integration/integrations", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).integrations;
} else {
console.log("Failed to get project integrations");
}
});
};
export default getIntegrations;
@@ -0,0 +1,18 @@
import SecurityClient from '~/utilities/SecurityClient';
const getIntegrations = () => {
return SecurityClient.fetchCall('/api/v1/integration/integrations', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}).then(async (res) => {
if (res && res.status == 200) {
return (await res.json()).integrations;
} else {
console.log('Failed to get project integrations');
}
});
};
export default getIntegrations;
@@ -1,33 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route starts the integration after teh default one if gonna set up.
* @param {*} integrationId
* @returns
*/
const startIntegration = ({ integrationId, appName, environment }) => {
return SecurityClient.fetchCall(
"/api/v1/integration/" + integrationId,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
update: {
app: appName,
environment,
isActive: true,
},
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to start an integration");
}
});
};
export default startIntegration;
@@ -0,0 +1,36 @@
import SecurityClient from '~/utilities/SecurityClient';
interface Props {
integrationId: string;
appName: string;
environment: string;
}
/**
* This route starts the integration after teh default one if gonna set up.
* @param {*} integrationId
* @returns
*/
const startIntegration = ({ integrationId, appName, environment }: Props) => {
return SecurityClient.fetchCall('/api/v1/integration/' + integrationId, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
update: {
app: appName,
environment,
isActive: true
}
})
}).then(async (res) => {
if (res && res.status == 200) {
return res;
} else {
console.log('Failed to start an integration');
}
});
};
export default startIntegration;
@@ -1,31 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the first step of the change password process (pake)
* @param {*} clientPublicKey
* @returns
*/
const AuthorizeIntegration = ({ workspaceId, code, integration }) => {
return SecurityClient.fetchCall(
"/api/v1/integration-auth/oauth-token",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
workspaceId,
code,
integration,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to authorize the integration");
}
});
};
export default AuthorizeIntegration;
@@ -0,0 +1,36 @@
import SecurityClient from '~/utilities/SecurityClient';
interface Props {
workspaceId: string;
code: string;
integration: string;
}
/**
* This is the first step of the change password process (pake)
* @param {object} obj
* @param {object} obj.workspaceId - project id for which we want to authorize the integration
* @param {object} obj.code
* @param {object} obj.integration - integration which a user is trying to turn on
* @returns
*/
const AuthorizeIntegration = ({ workspaceId, code, integration }: Props) => {
return SecurityClient.fetchCall('/api/v1/integration-auth/oauth-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
workspaceId,
code,
integration
})
}).then(async (res) => {
if (res && res.status == 200) {
return res;
} else {
console.log('Failed to authorize the integration');
}
});
};
export default AuthorizeIntegration;
@@ -1,26 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets authorizations of a certain project (Heroku, etc.)
* @param {*} workspaceId
* @returns
*/
const getWorkspaceAuthorizations = ({ workspaceId }) => {
return SecurityClient.fetchCall(
"/api/v1/workspace/" + workspaceId + "/authorizations",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).authorizations;
} else {
console.log("Failed to get project authorizations");
}
});
};
export default getWorkspaceAuthorizations;
@@ -0,0 +1,30 @@
import SecurityClient from '~/utilities/SecurityClient';
interface Props {
workspaceId: string;
}
/**
* This route gets authorizations of a certain project (Heroku, etc.)
* @param {*} workspaceId
* @returns
*/
const getWorkspaceAuthorizations = ({ workspaceId }: Props) => {
return SecurityClient.fetchCall(
'/api/v1/workspace/' + workspaceId + '/authorizations',
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
).then(async (res) => {
if (res && res.status == 200) {
return (await res.json()).authorizations;
} else {
console.log('Failed to get project authorizations');
}
});
};
export default getWorkspaceAuthorizations;
@@ -1,26 +0,0 @@
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets integrations of a certain project (Heroku, etc.)
* @param {*} workspaceId
* @returns
*/
const getWorkspaceIntegrations = ({ workspaceId }) => {
return SecurityClient.fetchCall(
"/api/v1/workspace/" + workspaceId + "/integrations",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).integrations;
} else {
console.log("Failed to get the project integrations");
}
});
};
export default getWorkspaceIntegrations;

Some files were not shown because too many files have changed in this diff Show More