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

View File

@@ -40,6 +40,7 @@ SITE_URL=http://localhost:8080
# Required to send emails
# By default, SMTP_HOST is set to smtp.gmail.com
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_NAME=Team
SMTP_USERNAME=team@infisical.com
SMTP_PASSWORD=

5
.husky/pre-commit Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"semi": true,
"trailingComma": "none",
"singleQuote": true,
"printWidth": 80,
"useTabs": false
}

View File

@@ -311,4 +311,4 @@ Looking to report a security vulnerability? Please don't post about it in GitHub
<!-- prettier-ignore-start -->
<!-- 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>

View File

@@ -1,18 +1,13 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"prettier"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"no-console": 2,
"prettier/prettier": 2
}
}
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"no-console": 2
}
}

View File

@@ -1,7 +0,0 @@
{
"semi": true,
"trailingComma": "none",
"singleQuote": true,
"printWidth": 80,
"useTabs": true
}

View File

@@ -28,7 +28,7 @@
"mongoose": "^6.7.2",
"nodemailer": "^6.8.0",
"posthog-node": "^2.1.0",
"query-string": "^7.1.1",
"query-string": "^7.1.3",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",
"tweetnacl": "^1.0.3",
@@ -50,7 +50,6 @@
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"husky": "^8.0.1",
"install": "^0.13.0",
"jest": "^29.3.1",
"nodemon": "^2.0.19",
@@ -2594,19 +2593,6 @@
"@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": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.19.0.tgz",
@@ -2704,26 +2690,6 @@
"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": {
"version": "0.24.51",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
@@ -4035,9 +4001,9 @@
}
},
"node_modules/decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"engines": {
"node": ">=0.10"
}
@@ -5146,21 +5112,6 @@
"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": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -9703,11 +9654,11 @@
}
},
"node_modules/query-string": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"dependencies": {
"decode-uri-component": "^0.2.0",
"decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
@@ -13113,16 +13064,6 @@
"@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": {
"version": "7.19.0",
"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": {
"version": "0.24.51",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
@@ -14219,9 +14146,9 @@
}
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og=="
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
},
"dedent": {
"version": "0.7.0",
@@ -15034,12 +14961,6 @@
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"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": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -18324,11 +18245,11 @@
}
},
"query-string": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"requires": {
"decode-uri-component": "^0.2.0",
"decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"

View File

@@ -19,7 +19,7 @@
"mongoose": "^6.7.2",
"nodemailer": "^6.8.0",
"posthog-node": "^2.1.0",
"query-string": "^7.1.1",
"query-string": "^7.1.3",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",
"tweetnacl": "^1.0.3",
@@ -35,7 +35,8 @@
"build": "rimraf ./build && tsc && cp -R ./src/templates ./src/json ./build",
"lint": "eslint . --ext .ts",
"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": {
"type": "git",
@@ -63,7 +64,6 @@
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"husky": "^8.0.1",
"install": "^0.13.0",
"jest": "^29.3.1",
"nodemon": "^2.0.19",

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_TOKEN_URL_HEROKU = process.env.OAUTH_TOKEN_URL_HEROKU!;
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 PUBLIC_KEY = process.env.PUBLIC_KEY!;
const SENTRY_DSN = process.env.SENTRY_DSN!;
const SITE_URL = process.env.SITE_URL!;
const SMTP_HOST = process.env.SMTP_HOST! || 'smtp.gmail.com';
const SMTP_PORT = process.env.SMTP_PORT! || 587;
const SMTP_NAME = process.env.SMTP_NAME!;
const SMTP_USERNAME = process.env.SMTP_USERNAME!;
const SMTP_PASSWORD = process.env.SMTP_PASSWORD!;
@@ -28,38 +31,39 @@ const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!;
const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY!;
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!;
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 {
PORT,
EMAIL_TOKEN_LIFETIME,
ENCRYPTION_KEY,
JWT_AUTH_LIFETIME,
JWT_AUTH_SECRET,
JWT_REFRESH_LIFETIME,
JWT_REFRESH_SECRET,
JWT_SERVICE_SECRET,
JWT_SIGNUP_LIFETIME,
JWT_SIGNUP_SECRET,
MONGO_URL,
NODE_ENV,
OAUTH_CLIENT_SECRET_HEROKU,
OAUTH_TOKEN_URL_HEROKU,
POSTHOG_HOST,
POSTHOG_PROJECT_API_KEY,
PRIVATE_KEY,
PUBLIC_KEY,
SENTRY_DSN,
SITE_URL,
SMTP_HOST,
SMTP_NAME,
SMTP_USERNAME,
SMTP_PASSWORD,
STRIPE_PRODUCT_CARD_AUTH,
STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER,
STRIPE_PUBLISHABLE_KEY,
STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET,
TELEMETRY_ENABLED
PORT,
EMAIL_TOKEN_LIFETIME,
ENCRYPTION_KEY,
JWT_AUTH_LIFETIME,
JWT_AUTH_SECRET,
JWT_REFRESH_LIFETIME,
JWT_REFRESH_SECRET,
JWT_SERVICE_SECRET,
JWT_SIGNUP_LIFETIME,
JWT_SIGNUP_SECRET,
MONGO_URL,
NODE_ENV,
OAUTH_CLIENT_SECRET_HEROKU,
OAUTH_TOKEN_URL_HEROKU,
POSTHOG_HOST,
POSTHOG_PROJECT_API_KEY,
PRIVATE_KEY,
PUBLIC_KEY,
SENTRY_DSN,
SITE_URL,
SMTP_HOST,
SMTP_PORT,
SMTP_NAME,
SMTP_USERNAME,
SMTP_PASSWORD,
STRIPE_PRODUCT_CARD_AUTH,
STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER,
STRIPE_PUBLISHABLE_KEY,
STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET,
TELEMETRY_ENABLED
};

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import * as Sentry from '@sentry/node';
@@ -5,17 +6,17 @@ import * as bigintConversion from 'bigint-conversion';
const jsrp = require('jsrp');
import { User } from '../models';
import { createToken, issueTokens, clearTokens } from '../helpers/auth';
import {
NODE_ENV,
JWT_AUTH_LIFETIME,
JWT_AUTH_SECRET,
JWT_REFRESH_SECRET
import {
NODE_ENV,
JWT_AUTH_LIFETIME,
JWT_AUTH_SECRET,
JWT_REFRESH_SECRET
} from '../config';
declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string;
}
export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string;
}
}
const clientPublicKeys: any = {};
@@ -27,47 +28,45 @@ const clientPublicKeys: any = {};
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
try {
const {
email,
clientPublicKey
}: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier');
try {
const {
email,
clientPublicKey
}: { email: string; clientPublicKey: string } = req.body;
if (!user) throw new Error('Failed to find user');
const user = await User.findOne({
email
}).select('+salt +verifier');
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier
},
() => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
clientPublicKeys[email] = {
clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
};
if (!user) throw new Error('Failed to find user');
return res.status(200).send({
serverPublicKey,
salt: user.salt
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to start authentication process'
});
}
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier
},
() => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
clientPublicKeys[email] = {
clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
};
return res.status(200).send({
serverPublicKey,
salt: user.salt
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to start authentication process'
});
}
};
/**
@@ -78,59 +77,59 @@ export const login1 = async (req: Request, res: Response) => {
* @returns
*/
export const login2 = async (req: Request, res: Response) => {
try {
const { email, clientProof } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag');
try {
const { email, clientProof } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag');
if (!user) throw new Error('Failed to find user');
if (!user) throw new Error('Failed to find user');
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: clientPublicKeys[email].serverBInt
},
async () => {
server.setClientPublicKey(clientPublicKeys[email].clientPublicKey);
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: clientPublicKeys[email].serverBInt
},
async () => {
server.setClientPublicKey(clientPublicKeys[email].clientPublicKey);
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
// issue tokens
const tokens = await issueTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/token',
sameSite: "strict",
secure: NODE_ENV === 'production' ? true : false
});
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
// issue tokens
const tokens = await issueTokens({ userId: user._id.toString() });
// return (access) token in response
return res.status(200).send({
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag
});
}
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/token',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
// return (access) token in response
return res.status(200).send({
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag
});
}
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
};
/**
@@ -140,29 +139,29 @@ export const login2 = async (req: Request, res: Response) => {
* @returns
*/
export const logout = async (req: Request, res: Response) => {
try {
await clearTokens({
userId: req.user._id.toString()
});
// clear httpOnly cookie
res.cookie('jid', '', {
httpOnly: true,
path: '/token',
sameSite: "strict",
secure: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to logout'
});
}
try {
await clearTokens({
userId: req.user._id.toString()
});
return res.status(200).send({
message: 'Successfully logged out.'
});
// clear httpOnly cookie
res.cookie('jid', '', {
httpOnly: true,
path: '/token',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to logout'
});
}
return res.status(200).send({
message: 'Successfully logged out.'
});
};
/**
@@ -172,9 +171,9 @@ export const logout = async (req: Request, res: Response) => {
* @returns
*/
export const checkAuth = async (req: Request, res: Response) =>
res.status(200).send({
message: 'Authenticated'
});
res.status(200).send({
message: 'Authenticated'
});
/**
* Return new token by redeeming refresh token
@@ -183,42 +182,41 @@ export const checkAuth = async (req: Request, res: Response) =>
* @returns
*/
export const getNewToken = async (req: Request, res: Response) => {
try {
const refreshToken = req.cookies.jid;
if (!refreshToken) {
throw new Error('Failed to find token in request cookies');
}
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(refreshToken, JWT_REFRESH_SECRET)
);
const user = await User.findOne({
_id: decodedToken.userId
}).select('+publicKey');
try {
const refreshToken = req.cookies.jid;
if (!user) throw new Error('Failed to authenticate unfound user');
if (!user?.publicKey)
throw new Error('Failed to authenticate not fully set up account');
const token = createToken({
payload: {
userId: decodedToken.userId
},
expiresIn: JWT_AUTH_LIFETIME,
secret: JWT_AUTH_SECRET
});
return res.status(200).send({
token
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Invalid request'
});
}
if (!refreshToken) {
throw new Error('Failed to find token in request cookies');
}
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(refreshToken, JWT_REFRESH_SECRET)
);
const user = await User.findOne({
_id: decodedToken.userId
}).select('+publicKey');
if (!user) throw new Error('Failed to authenticate unfound user');
if (!user?.publicKey)
throw new Error('Failed to authenticate not fully set up account');
const token = createToken({
payload: {
userId: decodedToken.userId
},
expiresIn: JWT_AUTH_LIFETIME,
secret: JWT_AUTH_SECRET
});
return res.status(200).send({
token
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Invalid request'
});
}
};

View File

@@ -217,7 +217,7 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
try {
const { email, code } = req.body;
user = await User.findOne({ email });
user = await User.findOne({ email }).select('+publicKey');
if (user && user?.publicKey) {
// case: user has already completed account
return res.status(403).send({
@@ -257,7 +257,7 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed email magic link confirmation'
error: 'Failed email magic link verification for organization invitation'
});
}

View File

@@ -1,11 +1,121 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import crypto from 'crypto';
const jsrp = require('jsrp');
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 = {};
/**
* 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
* @param req
@@ -43,7 +153,7 @@ export const srp1 = async (req: Request, res: Response) => {
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed to start change password process'
@@ -110,7 +220,7 @@ export const changePassword = async (req: Request, res: Response) => {
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed to change password. Try again?'
@@ -180,10 +290,73 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
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'
});
}

View File

@@ -2,21 +2,40 @@ import fs from 'fs';
import path from 'path';
import handlebars from 'handlebars';
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';
const mailOpts: SMTPConnection.Options = {
host: SMTP_HOST,
port: SMTP_PORT as number
};
if (SMTP_USERNAME && SMTP_PASSWORD) {
mailOpts.auth = {
user: SMTP_USERNAME,
pass: SMTP_PASSWORD
};
}
// create nodemailer transporter
const transporter = nodemailer.createTransport({
host: SMTP_HOST,
port: 587,
auth: {
user: SMTP_USERNAME,
pass: SMTP_PASSWORD
}
});
const transporter = nodemailer.createTransport(mailOpts);
transporter
.verify()
.then(() => console.log('SMTP - Successfully connected'))
.catch((err) => console.log('SMTP - Failed to connect'));
.verify()
.then(() => {
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
@@ -26,33 +45,34 @@ transporter
* @param {Object} obj.substitutions - object containing template substitutions
*/
const sendMail = async ({
template,
subjectLine,
recipients,
substitutions
template,
subjectLine,
recipients,
substitutions
}: {
template: string;
subjectLine: string;
recipients: string[];
substitutions: any;
template: string;
subjectLine: string;
recipients: string[];
substitutions: any;
}) => {
try {
const html = fs.readFileSync(
path.resolve(__dirname, '../templates/' + template),
'utf8'
);
const temp = handlebars.compile(html);
const htmlToSend = temp(substitutions);
try {
const html = fs.readFileSync(
path.resolve(__dirname, '../templates/' + template),
'utf8'
);
const temp = handlebars.compile(html);
const htmlToSend = temp(substitutions);
await transporter.sendMail({
from: `"${SMTP_NAME}" <${SMTP_USERNAME}>`,
to: recipients.join(', '),
subject: subjectLine,
html: htmlToSend
});
} catch (err) {
console.error(err);
}
await transporter.sendMail({
from: `"${SMTP_NAME}" <${SMTP_USERNAME}>`,
to: recipients.join(', '),
subject: subjectLine,
html: htmlToSend
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
}
};
export { sendMail };

View File

@@ -33,7 +33,7 @@ const sendEmailVerification = async ({ email }: { email: string }) => {
// send mail
await sendMail({
template: 'emailVerification.handlebars',
subjectLine: 'Infisical workspace invitation',
subjectLine: 'Infisical confirmation code',
recipients: [email],
substitutions: {
code: token

View File

@@ -5,28 +5,24 @@ import { requireAuth, validateRequest } from '../middleware';
import { authController } from '../controllers';
import { loginLimiter } from '../helpers/rateLimiter';
router.post('/token', validateRequest, authController.getNewToken);
router.post(
'/token',
validateRequest,
authController.getNewToken
'/login1',
loginLimiter,
body('email').exists().trim().notEmpty(),
body('clientPublicKey').exists().trim().notEmpty(),
validateRequest,
authController.login1
);
router.post(
'/login1',
loginLimiter,
body('email').exists().trim().notEmpty(),
body('clientPublicKey').exists().trim().notEmpty(),
validateRequest,
authController.login1
);
router.post(
'/login2',
loginLimiter,
body('email').exists().trim().notEmpty(),
body('clientProof').exists().trim().notEmpty(),
validateRequest,
authController.login2
'/login2',
loginLimiter,
body('email').exists().trim().notEmpty(),
body('clientProof').exists().trim().notEmpty(),
validateRequest,
authController.login2
);
router.post('/logout', requireAuth, authController.logout);

View File

@@ -1,7 +1,7 @@
import express from 'express';
const router = express.Router();
import { body } from 'express-validator';
import { requireAuth, validateRequest } from '../middleware';
import { requireAuth, requireSignupAuth, validateRequest } from '../middleware';
import { passwordController } from '../controllers';
import { passwordLimiter } from '../helpers/rateLimiter';
@@ -27,6 +27,33 @@ router.post(
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(
'/backup-private-key',
passwordLimiter,
@@ -41,4 +68,17 @@ router.post(
passwordController.createBackupPrivateKey
);
export default router;
// 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;

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Email Verification</title>
<title>Organization Invitation</title>
</head>
<body>
<h2>Infisical</h2>

View File

@@ -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 team@infisical.com</p>
</body>
</html>

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Email Verification</title>
<title>Project Invitation</title>
</head>
<body>
<h2>Infisical</h2>

View File

@@ -20,6 +20,7 @@ services:
restart: unless-stopped
depends_on:
- mongo
- smtp-server
build:
context: ./backend
dockerfile: Dockerfile
@@ -33,7 +34,7 @@ services:
- NODE_ENV=development
networks:
- infisical-dev
frontend:
container_name: infisical-dev-frontend
restart: unless-stopped
@@ -84,6 +85,18 @@ services:
networks:
- 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:
mongo-data:
driver: local

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).
@@ -13,11 +13,7 @@ The Infisical CLI provides a way to inject environment variables from the platfo
Use [brew](https://brew.sh/) package manager
```bash
# install
brew install infisical/get-cli/infisical
# check version
infisical --version
```
## Updates
@@ -31,14 +27,13 @@ The Infisical CLI provides a way to inject environment variables from the platfo
Use [Scoop](https://scoop.sh/) package manager
```bash
# install
scoop bucket add org https://github.com/Infisical/scoop-infisical.git
scoop install infisical
# check version
infisical --version
```
```bash
scoop install infisical
```
## Updates
```bash
@@ -49,33 +44,33 @@ The Infisical CLI provides a way to inject environment variables from the platfo
<Tab title="Alpine">
Install prerequisite
```bash
$ sudo apk add --no-cache bash sudo
sudo apk add --no-cache bash sudo
```
Add Infisical repository
```bash
$ curl -1sLf \
curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' \
| sudo -E bash
```
Then install CLI
```bash
$ sudo apk update && sudo apk add infisical
sudo apk update && sudo apk add infisical
```
</Tab>
<Tab title="RedHat/CentOs/Amazon">
Add Infisical repository
```bash
$ curl -1sLf \
curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.rpm.sh' \
| sudo -E bash
```
Then install CLI
```bash
$ sudo yum install infisical
sudo yum install infisical
```
</Tab>
@@ -83,14 +78,14 @@ The Infisical CLI provides a way to inject environment variables from the platfo
Add Infisical repository
```bash
$ curl -1sLf \
curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' \
| sudo -E bash
```
Then install CLI
```bash
$ sudo apt-get update && sudo apt-get install -y infisical
sudo apt-get update && sudo apt-get install -y infisical
```
</Tab>

View File

@@ -1,11 +1,13 @@
---
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)."
title: 'Frequently Asked Questions'
description: 'Have any questions? [Join our Slack community](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g).'
---
## Problem with SMTP
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.
If you opt for actual SMTP server (not the local MailHog), you have to have the right environment variables set.
You can normally populate `SMTP_USERNAME` and `SMTP_PASSWORD` with your usual login and password (you could also create a 'burner' email). Sometimes, there still are problems.
You can go to your Gmail account settings > security and enable “less secure apps”. This would allow Infisical to use your Gmail to send emails.
@@ -13,4 +15,4 @@ If it still doesn't work, [this](https://stackoverflow.com/questions/72547853/un
## `MONGO_URL` issues
Your `MONGO_URL` should be something like `mongodb://root:example@mongo:27017/?authSource=admin`. If you want to change it (not recommended), you should make sure that you keep this URL in line with `MONGO_USERNAME=root` and `MONGO_PASSWORD=example`.
Your `MONGO_URL` should be something like `mongodb://root:example@mongo:27017/?authSource=admin`. If you want to change it (not recommended), you should make sure that you keep this URL in line with `MONGO_USERNAME=root` and `MONGO_PASSWORD=example`.

View File

@@ -1,6 +1,6 @@
---
title: "Developing"
description: "This guide will help you set up and run Infisical in development mode."
title: 'Developing'
description: 'This guide will help you set up and run Infisical in development mode.'
---
## Clone the repo
@@ -16,17 +16,65 @@ cd infisical
## 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>
SMTP_USERNAME=team@infisical.com
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
```bash
# 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

View File

@@ -9,13 +9,13 @@
"plugins": ["simple-import-sort", "@typescript-eslint"],
"rules": {
"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-var-requires": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"simple-import-sort/exports": "warn",
"simple-import-sort/imports": [
"warn",

View File

@@ -1,4 +0,0 @@
{
"tabWidth": 2,
"useTabs": false
}

View File

@@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import { useEffect, useState } from 'react';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { publicPaths } from "~/const";
import checkAuth from "~/pages/api/auth/CheckAuth";
import { publicPaths } from '~/const';
import checkAuth from '~/pages/api/auth/CheckAuth';
// #TODO: finish spinner only when the data loads fully
// #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.
const hideContent = () => setAuthorized(false);
// const onError = () => setAuthorized(true)
router.events.on("routeChangeStart", hideContent);
router.events.on('routeChangeStart', hideContent);
// router.events.on("routeChangeError", onError);
// on route change complete - run auth check
router.events.on("routeChangeComplete", authCheck);
router.events.on('routeChangeComplete', authCheck);
// unsubscribe from events in useEffect return function
return () => {
router.events.off("routeChangeStart", hideContent);
router.events.off("routeChangeComplete", authCheck);
router.events.off('routeChangeStart', hideContent);
router.events.off('routeChangeComplete', authCheck);
// router.events.off("routeChangeError", onError);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -43,7 +43,7 @@ export default function RouteGuard({ children }) {
*/
async function authCheck(url) {
// 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
const response = await checkAuth();
@@ -51,16 +51,16 @@ export default function RouteGuard({ children }) {
if (!publicPaths.includes(path)) {
try {
if (response.status !== 200) {
router.push("/login");
console.log("Unauthorized to access.");
router.push('/login');
console.log('Unauthorized to access.');
setAuthorized(false);
} else {
setAuthorized(true);
console.log("Authorized to access.");
console.log('Authorized to access.');
}
} catch (error) {
console.log(
"Error (probably the authCheck route is stuck again...):",
'Error (probably the authCheck route is stuck again...):',
error
);
}

View File

@@ -1,16 +1,13 @@
import posthog from "posthog-js";
import posthog from 'posthog-js';
import {
ENV,
POSTHOG_API_KEY,
POSTHOG_HOST,
} from "../utilities/config";
import { ENV, POSTHOG_API_KEY, POSTHOG_HOST } from '../utilities/config';
export const initPostHog = () => {
if (typeof window !== "undefined") {
if (ENV == "production" && TELEMETRY_CAPTURING_ENABLED) { // eslint-disable-line
if (typeof window !== 'undefined') {
// eslint-disable-next-line
if (ENV == 'production' && TELEMETRY_CAPTURING_ENABLED) {
posthog.init(POSTHOG_API_KEY, {
api_host: POSTHOG_HOST,
api_host: POSTHOG_HOST
});
}
}

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;
};

View File

@@ -1,9 +1,9 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { faCircle, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useState } from 'react';
import { useRouter } from 'next/router';
import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import guidGenerator from "../utilities/randomId";
import guidGenerator from '../utilities/randomId';
interface InputFieldProps {
static?: boolean;
@@ -23,7 +23,7 @@ interface InputFieldProps {
const InputField = (
props: InputFieldProps &
Pick<JSX.IntrinsicElements["input"], "autoComplete" | "id">
Pick<JSX.IntrinsicElements['input'], 'autoComplete' | 'id'>
) => {
const [passwordVisible, setPasswordVisible] = useState(false);
const router = useRouter();
@@ -75,28 +75,28 @@ const InputField = (
</div>
<div
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`}
>
<input
onChange={(e) => props.onChangeHandler(e.target.value)}
type={passwordVisible === false ? props.type : "text"}
type={passwordVisible === false ? props.type : 'text'}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className={`${
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`}
name={props.name}
spellCheck="false"
autoComplete={props.autoComplete}
id={props.id}
/>
{props.label?.includes("Password") && (
{props.label?.includes('Password') && (
<button
onClick={() => {
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">
<p className="ml-2"></p>
{props.value
.split("")
.split('')
.slice(0, 54)
.map(() => (
<FontAwesomeIcon

View File

@@ -1,37 +1,38 @@
/* eslint-disable no-unexpected-multiline */
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import {
faBookOpen,
faGear,
faKey,
faMobile,
faPlug,
faUser,
} from "@fortawesome/free-solid-svg-icons";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
faUser
} from '@fortawesome/free-solid-svg-icons';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import getOrganizations from "~/pages/api/organization/getOrgs";
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers";
import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace";
import createWorkspace from "~/pages/api/workspace/createWorkspace";
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
import uploadKeys from "~/pages/api/workspace/uploadKeys";
import checkUserAction from "~/pages/api/userActions/checkUserAction";
import getOrganizations from '~/pages/api/organization/getOrgs';
import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects';
import getOrganizationUsers from '~/pages/api/organization/GetOrgUsers';
import checkUserAction from '~/pages/api/userActions/checkUserAction';
import addUserToWorkspace from '~/pages/api/workspace/addUserToWorkspace';
import createWorkspace from '~/pages/api/workspace/createWorkspace';
import getWorkspaces from '~/pages/api/workspace/getWorkspaces';
import uploadKeys from '~/pages/api/workspace/uploadKeys';
import NavBarDashboard from "../navigation/NavBarDashboard";
import { tempLocalStorage } from "../utilities/checks/tempLocalStorage";
import NavBarDashboard from '../navigation/NavBarDashboard';
import onboardingCheck from '../utilities/checks/OnboardingCheck';
import { tempLocalStorage } from '../utilities/checks/tempLocalStorage';
import {
decryptAssymmetric,
encryptAssymmetric,
} from "../utilities/cryptography/crypto";
import Button from "./buttons/Button";
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
import Listbox from "./Listbox";
encryptAssymmetric
} from '../utilities/cryptography/crypto';
import Button from './buttons/Button';
import AddWorkspaceDialog from './dialog/AddWorkspaceDialog';
import Listbox from './Listbox';
interface LayoutProps {
children: React.ReactNode;
@@ -41,16 +42,13 @@ export default function Layout({ children }: LayoutProps) {
const router = useRouter();
const [workspaceList, setWorkspaceList] = useState([]);
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
const [newWorkspaceName, setNewWorkspaceName] = useState("");
const [workspaceSelected, setWorkspaceSelected] = useState('∞');
const [newWorkspaceName, setNewWorkspaceName] = useState('');
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [hasUserClickedSlack, setHasUserClickedSlack] = useState(false);
const [hasUserClickedIntro, setHasUserClickedIntro] = useState(false);
const [hasUserStarred, setHasUserStarred] = useState(false);
const [usersInOrg, setUsersInOrg] = useState(false);
const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] = useState(0);
const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] =
useState(0);
function closeModal() {
setIsOpen(false);
@@ -77,35 +75,35 @@ export default function Layout({ children }: LayoutProps) {
if (!currentWorkspaces.includes(workspaceName)) {
const newWorkspace = await createWorkspace({
workspaceName,
organizationId: tempLocalStorage("orgData.id"),
organizationId: tempLocalStorage('orgData.id')
});
const newWorkspaceId = newWorkspace._id;
if (addAllUsers) {
const orgUsers = await getOrganizationUsers({
orgId: tempLocalStorage("orgData.id"),
orgId: tempLocalStorage('orgData.id')
});
orgUsers.map(async (user: any) => {
if (user.status == "accepted") {
if (user.status == 'accepted') {
const result = await addUserToWorkspace(
user.user.email,
newWorkspaceId
);
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
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: result.invitee.publicKey,
privateKey: PRIVATE_KEY,
privateKey: PRIVATE_KEY
}) as { ciphertext: string; nonce: string };
uploadKeys(
@@ -118,11 +116,11 @@ export default function Layout({ children }: LayoutProps) {
}
});
}
router.push("/dashboard/" + newWorkspaceId + "?Development");
router.push('/dashboard/' + newWorkspaceId + '?Development');
setIsOpen(false);
setNewWorkspaceName("");
setNewWorkspaceName('');
} else {
console.error("A project with this name already exists.");
console.error('A project with this name already exists.');
setError(true);
setLoading(false);
}
@@ -136,59 +134,59 @@ export default function Layout({ children }: LayoutProps) {
const menuItems = [
{
href:
"/dashboard/" +
'/dashboard/' +
workspaceMapping[workspaceSelected as any] +
"?Development",
title: "Secrets",
emoji: <FontAwesomeIcon icon={faKey} />,
'?Development',
title: 'Secrets',
emoji: <FontAwesomeIcon icon={faKey} />
},
{
href: "/users/" + workspaceMapping[workspaceSelected as any],
title: "Members",
emoji: <FontAwesomeIcon icon={faUser} />,
href: '/users/' + workspaceMapping[workspaceSelected as any],
title: 'Members',
emoji: <FontAwesomeIcon icon={faUser} />
},
{
href: "/integrations/" + workspaceMapping[workspaceSelected as any],
title: "Integrations",
emoji: <FontAwesomeIcon icon={faPlug} />,
href: '/integrations/' + workspaceMapping[workspaceSelected as any],
title: 'Integrations',
emoji: <FontAwesomeIcon icon={faPlug} />
},
{
href: "/settings/project/" + workspaceMapping[workspaceSelected as any],
title: "Project Settings",
emoji: <FontAwesomeIcon icon={faGear} />,
},
href: '/settings/project/' + workspaceMapping[workspaceSelected as any],
title: 'Project Settings',
emoji: <FontAwesomeIcon icon={faGear} />
}
];
useEffect(() => {
// Put a user in a workspace if they're not in one yet
const putUserInWorkSpace = async () => {
if (tempLocalStorage("orgData.id") === "") {
if (tempLocalStorage('orgData.id') === '') {
const userOrgs = await getOrganizations();
localStorage.setItem("orgData.id", userOrgs[0]._id);
localStorage.setItem('orgData.id', userOrgs[0]._id);
}
const orgUserProjects = await getOrganizationUserProjects({
orgId: tempLocalStorage("orgData.id"),
orgId: tempLocalStorage('orgData.id')
});
const userWorkspaces = orgUserProjects;
if (
userWorkspaces.length == 0 &&
router.asPath != "/noprojects" &&
!router.asPath.includes("settings")
router.asPath != '/noprojects' &&
!router.asPath.includes('settings')
) {
router.push("/noprojects");
} else if (router.asPath != "/noprojects") {
router.push('/noprojects');
} else if (router.asPath != '/noprojects') {
const intendedWorkspaceId = router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0];
.split('/')
[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 (
intendedWorkspaceId != "heroku" &&
intendedWorkspaceId != 'heroku' &&
!userWorkspaces
.map((workspace: { _id: string }) => workspace._id)
.includes(intendedWorkspaceId)
) {
router.push("/dashboard/" + userWorkspaces[0]._id + "?Development");
router.push('/dashboard/' + userWorkspaces[0]._id + '?Development');
} else {
setWorkspaceList(
userWorkspaces.map((workspace: any) => workspace.name)
@@ -197,7 +195,7 @@ export default function Layout({ children }: LayoutProps) {
Object.fromEntries(
userWorkspaces.map((workspace: any) => [
workspace.name,
workspace._id,
workspace._id
])
) as any
);
@@ -205,58 +203,19 @@ export default function Layout({ children }: LayoutProps) {
Object.fromEntries(
userWorkspaces.map((workspace: any) => [
workspace._id,
workspace.name,
workspace.name
])
)[
router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0]
.split('/')
[router.asPath.split('/').length - 1].split('?')[0]
]
);
}
}
};
putUserInWorkSpace();
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();
onboardingCheck({ setTotalOnboardingActionsDone });
}, []);
useEffect(() => {
@@ -265,16 +224,16 @@ export default function Layout({ children }: LayoutProps) {
workspaceMapping[Number(workspaceSelected)] &&
`${workspaceMapping[Number(workspaceSelected)]}` !==
router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0]
.split('/')
[router.asPath.split('/').length - 1].split('?')[0]
) {
router.push(
"/dashboard/" +
'/dashboard/' +
workspaceMapping[Number(workspaceSelected)] +
"?Development"
'?Development'
);
localStorage.setItem(
"projectData.id",
'projectData.id',
`${workspaceMapping[Number(workspaceSelected)]}`
);
}
@@ -286,7 +245,10 @@ export default function Layout({ children }: LayoutProps) {
return (
<>
<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 />
<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">
@@ -320,11 +282,11 @@ export default function Layout({ children }: LayoutProps) {
{workspaceList.length > 0 &&
menuItems.map(({ href, title, emoji }) => (
<li className="mt-0.5 mx-2" key={title}>
{router.asPath.split("/")[1] === href.split("/")[1] &&
(["project", "billing", "org", "personal"].includes(
router.asPath.split("/")[2]
{router.asPath.split('/')[1] === href.split('/')[1] &&
(['project', 'billing', 'org', 'personal'].includes(
router.asPath.split('/')[2]
)
? router.asPath.split("/")[2] === href.split("/")[2]
? router.asPath.split('/')[2] === href.split('/')[2]
: true) ? (
<div
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>
{title}
</div>
) : router.asPath == "/noprojects" ? (
) : router.asPath == '/noprojects' ? (
<div
className={`flex p-2.5 text-white text-sm rounded`}
>
@@ -361,7 +323,7 @@ export default function Layout({ children }: LayoutProps) {
</ul>
</div>
<div className="w-full mt-40 mb-4 px-2">
{router.asPath.split("/")[1] === "home" ? (
{router.asPath.split('/')[1] === 'home' ? (
<div
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>
Infisical Guide
<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}
width={58}
alt="progress bar"
@@ -390,7 +358,13 @@ export default function Layout({ children }: LayoutProps) {
</p>
Infisical Guide
<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}
width={58}
alt="progress bar"
@@ -420,9 +394,9 @@ export default function Layout({ children }: LayoutProps) {
className="text-gray-300 text-7xl mb-8"
/>
<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
dimensions.{" "}
dimensions.{' '}
</p>
</div>
</>

View File

@@ -1,39 +1,39 @@
import { Fragment, useState } from "react";
import { useRouter } from "next/router";
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Dialog, Transition } from "@headlessui/react";
import nacl from "tweetnacl";
import { Fragment, useState } from 'react';
import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Dialog, Transition } from '@headlessui/react';
import nacl from 'tweetnacl';
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import addServiceToken from '~/pages/api/serviceToken/addServiceToken';
import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
import { envMapping } from "../../../public/data/frequentConstants";
import { envMapping } from '../../../public/data/frequentConstants';
import {
decryptAssymmetric,
encryptAssymmetric,
} from "../../utilities/cryptography/crypto";
import Button from "../buttons/Button";
import InputField from "../InputField";
import ListBox from "../Listbox";
encryptAssymmetric
} from '../../utilities/cryptography/crypto';
import Button from '../buttons/Button';
import InputField from '../InputField';
import ListBox from '../Listbox';
const expiryMapping = {
"1 day": 86400,
"7 days": 604800,
"1 month": 2592000,
'1 day': 86400,
'7 days': 604800,
'1 month': 2592000,
'6 months': 15552000,
'12 months': 31104000
};
const AddServiceTokenDialog = ({
isOpen,
closeModal,
workspaceId,
workspaceName,
workspaceName
}) => {
const router = useRouter();
const [serviceToken, setServiceToken] = useState("");
const [serviceTokenName, setServiceTokenName] = useState("");
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
const [serviceToken, setServiceToken] = useState('');
const [serviceTokenName, setServiceTokenName] = useState('');
const [serviceTokenEnv, setServiceTokenEnv] = useState('Development');
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState('1 day');
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const generateServiceToken = async () => {
@@ -43,7 +43,7 @@ const AddServiceTokenDialog = ({
ciphertext: latestFileKey.latestKey.encryptedKey,
nonce: latestFileKey.latestKey.nonce,
publicKey: latestFileKey.latestKey.sender.publicKey,
privateKey: localStorage.getItem("PRIVATE_KEY"),
privateKey: localStorage.getItem('PRIVATE_KEY')
});
// generate new public/private key pair
@@ -55,7 +55,7 @@ const AddServiceTokenDialog = ({
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
privateKey,
privateKey
});
let newServiceToken = await addServiceToken({
@@ -65,16 +65,16 @@ const AddServiceTokenDialog = ({
expiresIn: expiryMapping[serviceTokenExpiresIn],
publicKey,
encryptedKey,
nonce,
nonce
});
const serviceToken = newServiceToken + "," + privateKey;
const serviceToken = newServiceToken + ',' + privateKey;
setServiceToken(serviceToken);
};
function copyToClipboard() {
// Get the text field
var copyText = document.getElementById("serviceToken");
var copyText = document.getElementById('serviceToken');
// Select the text field
copyText.select();
@@ -91,8 +91,8 @@ const AddServiceTokenDialog = ({
const closeAddServiceTokenModal = () => {
closeModal();
setServiceTokenName("");
setServiceToken("");
setServiceTokenName('');
setServiceToken('');
};
return (
@@ -122,7 +122,7 @@ const AddServiceTokenDialog = ({
leaveFrom="opacity-100 scale-100"
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.Title
as="h3"
@@ -155,12 +155,12 @@ const AddServiceTokenDialog = ({
selected={serviceTokenEnv}
onChange={setServiceTokenEnv}
data={[
"Development",
"Staging",
"Production",
"Testing",
'Development',
'Staging',
'Production',
'Testing'
]}
width="full"
isFull={true}
text="Environment: "
/>
</div>
@@ -168,8 +168,14 @@ const AddServiceTokenDialog = ({
<ListBox
selected={serviceTokenExpiresIn}
onChange={setServiceTokenExpiresIn}
data={["1 day", "7 days", "1 month"]}
width="full"
data={[
'1 day',
'7 days',
'1 month',
'6 months',
'12 months'
]}
isFull={true}
text="Expires in: "
/>
</div>
@@ -181,7 +187,7 @@ const AddServiceTokenDialog = ({
text="Add Service Token"
textDisabled="Add Service Token"
size="md"
active={serviceTokenName == "" ? false : true}
active={serviceTokenName == '' ? false : true}
/>
</div>
</div>

View File

@@ -1,10 +1,10 @@
import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { faX } from "@fortawesome/free-solid-svg-icons";
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { faX } from '@fortawesome/free-solid-svg-icons';
import { reverseEnvMapping } from "../../../public/data/frequentConstants";
import guidGenerator from "../../utilities/randomId";
import Button from "../buttons/Button";
import { reverseEnvMapping } from '../../../public/data/frequentConstants';
import guidGenerator from '../../utilities/randomId';
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.

View File

@@ -1,25 +1,25 @@
import React, { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/router";
import { faX } from "@fortawesome/free-solid-svg-icons";
import React, { useEffect, useMemo, useState } from 'react';
import { useRouter } from 'next/router';
import { faX } from '@fortawesome/free-solid-svg-icons';
import deleteUserFromOrganization from "~/pages/api/organization/deleteUserFromOrganization";
import changeUserRoleInWorkspace from "~/pages/api/workspace/changeUserRoleInWorkspace";
import deleteUserFromWorkspace from "~/pages/api/workspace/deleteUserFromWorkspace";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import uploadKeys from "~/pages/api/workspace/uploadKeys";
import deleteUserFromOrganization from '~/pages/api/organization/deleteUserFromOrganization';
import changeUserRoleInWorkspace from '~/pages/api/workspace/changeUserRoleInWorkspace';
import deleteUserFromWorkspace from '~/pages/api/workspace/deleteUserFromWorkspace';
import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
import uploadKeys from '~/pages/api/workspace/uploadKeys';
import guidGenerator from "../../utilities/randomId";
import Button from "../buttons/Button";
import Listbox from "../Listbox";
import guidGenerator from '../../utilities/randomId';
import Button from '../buttons/Button';
import Listbox from '../Listbox';
const {
decryptAssymmetric,
encryptAssymmetric,
} = require("../../utilities/cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
encryptAssymmetric
} = require('../../utilities/cryptography/crypto');
const nacl = require('tweetnacl');
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.
@@ -36,13 +36,13 @@ const UserTable = ({
isOrg,
onClick,
deleteUser,
setUserIdToBeDeleted,
setUserIdToBeDeleted
}) => {
const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role))
);
const router = useRouter();
const [myRole, setMyRole] = useState("member");
const [myRole, setMyRole] = useState('member');
// 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.
@@ -57,7 +57,7 @@ const UserTable = ({
changeData(userData.filter((v, i) => i !== index));
setRoleSelected([
...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,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey,
},
publicKey: userData[index].publicKey
}
],
...userData.slice(index + 1, userData?.length),
...userData.slice(index + 1, userData?.length)
]);
};
@@ -88,22 +88,22 @@ const UserTable = ({
}, [userData, myUser]);
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
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: publicKey,
privateKey: PRIVATE_KEY,
privateKey: PRIVATE_KEY
});
uploadKeys(router.query.id, id, ciphertext, nonce);
@@ -158,24 +158,24 @@ const UserTable = ({
</td>
<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">
{row.status == "granted" &&
((myRole == "admin" && row.role != "owner") ||
myRole == "owner") &&
{row.status == 'granted' &&
((myRole == 'admin' && row.role != 'owner') ||
myRole == 'owner') &&
myUser !== row.email ? (
<Listbox
selected={row.role}
onChange={(e) => handleRoleUpdate(index, e)}
data={
myRole == "owner"
? ["owner", "admin", "member"]
: ["admin", "member"]
myRole == 'owner'
? ['owner', 'admin', 'member']
: ['admin', 'member']
}
text="Role: "
membershipId={row.membershipId}
/>
) : (
row.status != "invited" &&
row.status != "verified" && (
row.status != 'invited' &&
row.status != 'verified' && (
<Listbox
selected={row.role}
text="Role: "
@@ -183,8 +183,8 @@ const UserTable = ({
/>
)
)}
{(row.status == "invited" ||
row.status == "verified") && (
{(row.status == 'invited' ||
row.status == 'verified') && (
<div className="w-full pl-9">
<Button
onButtonPressed={() =>
@@ -199,7 +199,7 @@ const UserTable = ({
/>
</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">
<Button
onButtonPressed={() =>
@@ -214,7 +214,7 @@ const UserTable = ({
</div>
{myUser !== row.email &&
// row.role != "admin" &&
myRole != "member" ? (
myRole != 'member' ? (
<div className="opacity-50 hover:opacity-100 flex items-center">
<Button
onButtonPressed={(e) =>

View File

@@ -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 (
<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 ${
(plan.name != "Starter") & (plan.current == true)
? "border-primary"
: "border-chicago-700"
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
? 'border-primary'
: 'border-chicago-700'
}
`}
>
@@ -36,7 +51,7 @@ export default function Plan({ plan }) {
<div className="flex flex-row items-center">
{plan.current == false ? (
<>
{plan.buttonTextMain == "Schedule a Demo" ? (
{plan.buttonTextMain == 'Schedule a Demo' ? (
<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">
{plan.buttonTextMain}
@@ -45,15 +60,15 @@ export default function Plan({ plan }) {
) : (
<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 ${
plan.buttonTextMain == "Downgrade"
? "hover:bg-red hover:text-white hover:border-red"
: "hover:bg-primary hover:text-black hover:border-primary"
plan.buttonTextMain == 'Downgrade'
? 'hover:bg-red hover:text-white hover:border-red'
: 'hover:bg-primary hover:text-black hover:border-primary'
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
>
<button
onClick={() =>
StripeRedirect({
orgId: localStorage.getItem("orgData.id"),
orgId: tempLocalStorage('orgData.id')
})
}
>
@@ -61,7 +76,10 @@ export default function Plan({ plan }) {
</button>
</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">
{plan.buttonTextSecondary}
</div>
@@ -70,9 +88,9 @@ export default function Plan({ plan }) {
) : (
<div
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
(plan.name != "Starter") & (plan.current == true)
? "bg-primary"
: "bg-chicago-700"
plan.name != 'Starter' && plan.current == true
? 'bg-primary'
: 'bg-chicago-700'
}`}
>
<p className="text-xs text-black font-semibold">CURRENT PLAN</p>

View File

@@ -1,9 +1,8 @@
import { useEffect, useRef } from "react";
import { faXmarkCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import classnames from "classnames";
import { useEffect, useRef } from 'react';
import { faX } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Notification as NotificationType } from "./NotificationProvider";
import { Notification as NotificationType } from './NotificationProvider';
interface NotificationProps {
notification: Required<NotificationType>;
@@ -12,7 +11,7 @@ interface NotificationProps {
const Notification = ({
notification,
clearNotification,
clearNotification
}: NotificationProps) => {
const timeout = useRef<number>();
@@ -37,22 +36,29 @@ const Notification = ({
return (
<div
className={classnames(
"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",
}
)}
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"
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
className="bg-white/5 rounded-lg p-3"
className="rounded-lg"
onClick={() => clearNotification(notification.text)}
>
<FontAwesomeIcon className="text-white" icon={faXmarkCircle} />
<FontAwesomeIcon
className="text-white w-4 h-3 hover:text-red"
icon={faX}
/>
</button>
</div>
);

View File

@@ -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 = {
text: string;
@@ -15,7 +15,7 @@ type NotificationContextState = {
};
const NotificationContext = createContext<NotificationContextState>({
createNotification: () => console.log("createNotification not set!"),
createNotification: () => console.log('createNotification not set!')
});
export const useNotificationContext = () => useContext(NotificationContext);
@@ -37,8 +37,8 @@ const NotificationProvider = ({ children }: NotificationProviderProps) => {
const createNotification = ({
text,
type = "success",
timeoutMs = 2000,
type = 'success',
timeoutMs = 5000
}: Notification) => {
const doesNotifExist = notifications.some((notif) => notif.text === text);
@@ -54,7 +54,7 @@ const NotificationProvider = ({ children }: NotificationProviderProps) => {
return (
<NotificationContext.Provider
value={{
createNotification,
createNotification
}}
>
<Notifications

View File

@@ -1,5 +1,5 @@
import Notification from "./Notification";
import { Notification as NotificationType } from "./NotificationProvider";
import Notification from './Notification';
import { Notification as NotificationType } from './NotificationProvider';
interface NoticationsProps {
notifications: Required<NotificationType>[];
@@ -8,14 +8,14 @@ interface NoticationsProps {
const Notifications = ({
notifications,
clearNotification,
clearNotification
}: NoticationsProps) => {
if (!notifications.length) {
return null;
}
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) => (
<Notification
key={notif.text}

View File

@@ -1,8 +1,8 @@
import React, { SyntheticEvent, useRef } from "react";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { SyntheticEvent, useRef } from 'react';
import { faCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import guidGenerator from "../utilities/randomId";
import guidGenerator from '../utilities/randomId';
const REGEX = /([$]{.*?})/g;
@@ -10,7 +10,7 @@ interface DashboardInputFieldProps {
position: number;
onChangeHandler: (value: string, position: number) => void;
value: string;
type: "varName" | "value";
type: 'varName' | 'value';
blurred: boolean;
duplicates: string[];
}
@@ -33,7 +33,7 @@ const DashboardInputField = ({
type,
value,
blurred,
duplicates,
duplicates
}: DashboardInputFieldProps) => {
const ref = useRef<HTMLDivElement | null>(null);
const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
@@ -43,8 +43,8 @@ const DashboardInputField = ({
ref.current.scrollLeft = e.currentTarget.scrollLeft;
};
if (type === "varName") {
const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != "";
if (type === 'varName') {
const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != '';
const hasDuplicates = duplicates?.includes(value);
const error = startsWithNumber || hasDuplicates;
@@ -52,7 +52,7 @@ const DashboardInputField = ({
<div className="flex-col w-full">
<div
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`}
>
<input
@@ -62,7 +62,7 @@ const DashboardInputField = ({
type={type}
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 ${
error ? "focus:ring-red/50" : "focus:ring-primary/50"
error ? 'focus:ring-red/50' : 'focus:ring-primary/50'
} duration-200`}
spellCheck="false"
/>
@@ -79,7 +79,7 @@ const DashboardInputField = ({
)}
</div>
);
} else if (type === "value") {
} else if (type === 'value') {
return (
<div className="flex-col w-full">
<div
@@ -91,8 +91,8 @@ const DashboardInputField = ({
onScroll={syncScroll}
className={`${
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`}
spellCheck="false"
/>
@@ -100,8 +100,8 @@ const DashboardInputField = ({
ref={ref}
className={`${
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`}
>
{value.split(REGEX).map((word, id) => {
@@ -112,7 +112,7 @@ const DashboardInputField = ({
<span className="ph-no-capture text-yellow-200/80">
{word.slice(2, word.length - 1)}
</span>
{word.slice(word.length - 1, word.length) == "}" ? (
{word.slice(word.length - 1, word.length) == '}' ? (
<span className="ph-no-capture text-yellow">
{word.slice(word.length - 1, word.length)}
</span>
@@ -135,7 +135,7 @@ const DashboardInputField = ({
{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="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
{value.split("").map(() => (
{value.split('').map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mx-0.5"

View File

@@ -1,12 +1,12 @@
import { type ChangeEvent, type DragEvent, useState } from "react";
import Image from "next/image";
import { faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { type ChangeEvent, type DragEvent, useState } from 'react';
import Image from 'next/image';
import { faUpload } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from "../basic/buttons/Button";
import Error from "../basic/Error";
import parse from "../utilities/file";
import guidGenerator from "../utilities/randomId";
import Button from '../basic/buttons/Button';
import Error from '../basic/Error';
import parse from '../utilities/file';
import guidGenerator from '../utilities/randomId';
interface DropZoneProps {
// TODO: change Data type from any
@@ -26,7 +26,7 @@ const DropZone = ({
errorDragAndDrop,
setButtonReady,
keysExist,
numCurrentRows,
numCurrentRows
}: DropZoneProps) => {
const handleDragEnter = (e: DragEvent) => {
e.preventDefault();
@@ -43,7 +43,7 @@ const DropZone = ({
e.stopPropagation();
// set dropEffect to copy i.e copy of the source item
e.dataTransfer.dropEffect = "copy";
e.dataTransfer.dropEffect = 'copy';
};
const [loading, setLoading] = useState(false);
@@ -54,7 +54,7 @@ const DropZone = ({
setTimeout(() => setLoading(false), 5000);
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = "copy";
e.dataTransfer.dropEffect = 'copy';
const file = e.dataTransfer.files[0];
const reader = new FileReader();
@@ -68,7 +68,7 @@ const DropZone = ({
numCurrentRows + index,
key,
keyPairs[key as keyof typeof keyPairs],
"shared",
'shared'
]);
setData(newData);
setButtonReady(true);
@@ -94,15 +94,15 @@ const DropZone = ({
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
const { result } = event.target;
if (typeof result === "string") {
if (typeof result === 'string') {
const newData = result
.split("\n")
.split('\n')
.map((line: string, index: number) => [
guidGenerator(),
numCurrentRows + index,
line.split("=")[0],
line.split("=").slice(1, line.split("=").length).join("="),
"shared",
line.split('=')[0],
line.split('=').slice(1, line.split('=').length).join('='),
'shared'
]);
setData(newData);
setButtonReady(true);

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react/jsx-key */
import React, { Fragment, useEffect, useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons";
import React, { Fragment, useEffect, useState } from 'react';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { faGithub, faSlack } from '@fortawesome/free-brands-svg-icons';
import { faCircleQuestion } from '@fortawesome/free-regular-svg-icons';
import {
faAngleDown,
faBook,
@@ -12,39 +12,39 @@ import {
faEnvelope,
faGear,
faPlus,
faRightFromBracket,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Menu, Transition } from "@headlessui/react";
faRightFromBracket
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Menu, Transition } from '@headlessui/react';
import logout from "~/pages/api/auth/Logout";
import getOrganization from "~/pages/api/organization/GetOrg";
import getOrganizations from "~/pages/api/organization/getOrgs";
import getUser from "~/pages/api/user/getUser";
import logout from '~/pages/api/auth/Logout';
import getOrganization from '~/pages/api/organization/GetOrg';
import getOrganizations from '~/pages/api/organization/getOrgs';
import getUser from '~/pages/api/user/getUser';
import guidGenerator from "../utilities/randomId";
import guidGenerator from '../utilities/randomId';
const supportOptions = [
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
"Join Slack Forum",
"https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g",
'Join Slack Forum',
'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} />,
"Read Docs",
"https://infisical.com/docs/getting-started/introduction",
'Read Docs',
'https://infisical.com/docs/getting-started/introduction'
],
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
"Open a GitHub Issue",
"https://github.com/Infisical/infisical-cli/issues",
'Open a GitHub Issue',
'https://github.com/Infisical/infisical-cli/issues'
],
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
"Send us an Email",
"mailto:support@infisical.com",
],
'Send us an Email',
'mailto:support@infisical.com'
]
];
export interface ICurrentOrg {
@@ -58,7 +58,7 @@ export interface IUser {
}
/**
* This is the navigation bar in the main app.
* This is the navigation bar in the main app.
* It has two main components: support options and user menu (inlcudes billing, logout, org/user settings)
* @returns NavBar
*/
@@ -75,16 +75,16 @@ export default function Navbar() {
const orgsData = await getOrganizations();
setOrgs(orgsData);
const currentOrg = await getOrganization({
orgId: String(localStorage.getItem("orgData.id")),
orgId: String(localStorage.getItem('orgData.id'))
});
setCurrentOrg(currentOrg);
})();
}, []);
const closeApp = async () => {
console.log("Logging out...");
console.log('Logging out...');
await logout();
router.push("/login");
router.push('/login');
};
return (
@@ -163,7 +163,7 @@ export default function Navbar() {
</div>
<div
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"
>
@@ -173,11 +173,11 @@ export default function Navbar() {
<div className="flex items-center justify-between w-full">
<div>
<p className="text-gray-300 px-2 pt-1 text-sm">
{" "}
{' '}
{user?.firstName} {user?.lastName}
</p>
<p className="text-gray-400 px-2 pb-1 text-xs">
{" "}
{' '}
{user?.email}
</p>
</div>
@@ -194,7 +194,7 @@ export default function Navbar() {
</div>
<div
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"
>
@@ -217,7 +217,7 @@ export default function Navbar() {
>
<div
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"
>
@@ -235,7 +235,7 @@ export default function Navbar() {
<div
onClick={() =>
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"
@@ -255,13 +255,14 @@ export default function Navbar() {
<div className="flex flex-col items-start px-1 mt-3 mb-2">
{orgs
.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
key={guidGenerator()}
onClick={() => {
localStorage.setItem("orgData.id", org._id);
localStorage.setItem('orgData.id', org._id);
router.reload();
}}
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}
className={`${
active
? "bg-red font-semibold text-white"
: "text-gray-400"
? 'bg-red font-semibold text-white'
: 'text-gray-400'
} 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">

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -1,17 +1,17 @@
import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm";
import login1 from "~/pages/api/auth/Login1";
import login2 from "~/pages/api/auth/Login2";
import getOrganizations from "~/pages/api/organization/getOrgs";
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm';
import login1 from '~/pages/api/auth/Login1';
import login2 from '~/pages/api/auth/Login2';
import getOrganizations from '~/pages/api/organization/getOrgs';
import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects';
import pushKeys from "./secrets/pushKeys";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
import SecurityClient from "./SecurityClient";
import Telemetry from "./telemetry/Telemetry";
import pushKeys from './secrets/pushKeys';
import Telemetry from './telemetry/Telemetry';
import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
import SecurityClient from './SecurityClient';
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const jsrp = require("jsrp");
const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');
const jsrp = require('jsrp');
const client = new jsrp.client();
/**
@@ -37,7 +37,7 @@ const attemptLogin = async (
client.init(
{
username: email,
password: password,
password: password
},
async () => {
const clientPublicKey = client.getPublicKey();
@@ -54,54 +54,53 @@ const attemptLogin = async (
await login2(email, clientProof);
SecurityClient.setToken(token);
const privateKey = Aes256Gcm.decrypt(
encryptedPrivateKey,
const privateKey = Aes256Gcm.decrypt({
ciphertext: encryptedPrivateKey,
iv,
tag,
password
secret: password
.slice(0, 32)
.padStart(
32 + (password.slice(0, 32).length - new Blob([password]).size),
"0"
'0'
)
);
});
saveTokenToLocalStorage({
token,
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey,
privateKey
});
const userOrgs = await getOrganizations();
const userOrgsData = userOrgs.map((org) => org._id);
let orgToLogin;
if (userOrgsData.includes(localStorage.getItem("orgData.id"))) {
orgToLogin = localStorage.getItem("orgData.id");
if (userOrgsData.includes(localStorage.getItem('orgData.id'))) {
orgToLogin = localStorage.getItem('orgData.id');
} else {
orgToLogin = userOrgsData[0];
localStorage.setItem("orgData.id", orgToLogin);
localStorage.setItem('orgData.id', orgToLogin);
}
let orgUserProjects = await getOrganizationUserProjects({
orgId: orgToLogin,
orgId: orgToLogin
});
orgUserProjects = orgUserProjects?.map((project) => project._id);
let projectToLogin;
if (
orgUserProjects.includes(localStorage.getItem("projectData.id"))
orgUserProjects.includes(localStorage.getItem('projectData.id'))
) {
projectToLogin = localStorage.getItem("projectData.id");
projectToLogin = localStorage.getItem('projectData.id');
} else {
try {
projectToLogin = orgUserProjects[0];
localStorage.setItem("projectData.id", projectToLogin);
localStorage.setItem('projectData.id', projectToLogin);
} 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({
obj: {
DATABASE_URL: [
"mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net",
"personal",
'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
'personal'
],
DB_USERNAME: ["user1234", "personal"],
DB_PASSWORD: ["ah8jak3hk8dhiu4dw7whxwe1l", "personal"],
TWILIO_AUTH_TOKEN: [
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG",
"shared",
],
WEBSITE_URL: ["http://localhost:3000", "shared"],
STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"],
DB_USERNAME: ['user1234', 'personal'],
DB_PASSWORD: ['example_password', 'personal'],
TWILIO_AUTH_TOKEN: ['example_twillion_token', 'shared'],
WEBSITE_URL: ['http://localhost:3000', 'shared'],
STRIPE_SECRET_KEY: ['sk_test_7348oyho4hfq398HIUOH78', 'shared']
},
workspaceId: projectToLogin,
env: "Development",
env: 'Development'
});
}
if (email) {
telemetry.identify(email);
telemetry.capture("User Logged In");
telemetry.capture('User Logged In');
}
if (isLogin) {
router.push("/dashboard/");
router.push('/dashboard/');
}
} catch (error) {
setErrorLogin(true);
console.log("Login response not available");
console.log('Login response not available');
}
}
);
} catch (error) {
console.log("Something went wrong during authentication");
console.log('Something went wrong during authentication');
}
return true;
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,11 +1,11 @@
import changePassword2 from "~/pages/api/auth/ChangePassword2";
import SRP1 from "~/pages/api/auth/SRP1";
import changePassword2 from '~/pages/api/auth/ChangePassword2';
import SRP1 from '~/pages/api/auth/SRP1';
import Aes256Gcm from "./aes-256-gcm";
import Aes256Gcm from './aes-256-gcm';
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const jsrp = require("jsrp");
const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');
const jsrp = require('jsrp');
const clientOldPassword = new jsrp.client();
const clientNewPassword = new jsrp.client();
@@ -34,7 +34,7 @@ const changePassword = async (
clientOldPassword.init(
{
username: email,
password: currentPassword,
password: currentPassword
},
async () => {
const clientPublicKey = clientOldPassword.getPublicKey();
@@ -42,13 +42,13 @@ const changePassword = async (
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey,
clientPublicKey: clientPublicKey
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setCurrentPasswordError(true);
console.log("Wrong current password", err, 1);
console.log('Wrong current password', err, 1);
}
clientOldPassword.setSalt(salt);
@@ -58,27 +58,27 @@ const changePassword = async (
clientNewPassword.init(
{
username: email,
password: newPassword,
password: newPassword
},
async () => {
clientNewPassword.createVerifier(async (err, result) => {
// The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø)
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
newPassword
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
text: localStorage.getItem('PRIVATE_KEY'),
secret: newPassword
.slice(0, 32)
.padStart(
32 +
(newPassword.slice(0, 32).length -
new Blob([newPassword]).size),
"0"
'0'
)
);
});
if (ciphertext) {
localStorage.setItem("encryptedPrivateKey", ciphertext);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
localStorage.setItem('encryptedPrivateKey', ciphertext);
localStorage.setItem('iv', iv);
localStorage.setItem('tag', tag);
let res;
try {
@@ -88,14 +88,14 @@ const changePassword = async (
tag,
salt: result.salt,
verifier: result.verifier,
clientProof,
clientProof
});
if (res.status == 400) {
setCurrentPasswordError(true);
} else if (res.status == 200) {
setPasswordChanged(true);
setCurrentPassword("");
setNewPassword("");
setCurrentPassword('');
setNewPassword('');
}
} catch (err) {
setCurrentPasswordError(true);
@@ -108,7 +108,7 @@ const changePassword = async (
}
);
} catch (error) {
console.log("Something went wrong during changing the password");
console.log('Something went wrong during changing the password');
}
return true;
};

View File

@@ -1,12 +1,12 @@
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const aes = require("./aes-256-gcm");
const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');
import aes from './aes-256-gcm';
type encryptAsymmetricProps = {
plaintext: string;
publicKey: string;
privateKey: string;
}
};
/**
* Return assymmetrically encrypted [plaintext] using [publicKey] where
@@ -19,7 +19,11 @@ type encryptAsymmetricProps = {
* @returns {String} ciphertext - base64-encoded ciphertext
* @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 ciphertext = nacl.box(
nacl.util.decodeUTF8(plaintext),
@@ -30,7 +34,7 @@ const encryptAssymmetric = ({ plaintext, publicKey, privateKey }: encryptAsymmet
return {
ciphertext: nacl.util.encodeBase64(ciphertext),
nonce: nacl.util.encodeBase64(nonce),
nonce: nacl.util.encodeBase64(nonce)
};
};
@@ -39,7 +43,7 @@ type decryptAsymmetricProps = {
nonce: string;
publicKey: string;
privateKey: string;
}
};
/**
* Return assymmetrically decrypted [ciphertext] using [privateKey] where
@@ -49,9 +53,13 @@ type decryptAsymmetricProps = {
* @param {String} obj.nonce - nonce
* @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} plaintext - UTF8 plaintext
*/
const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }: decryptAsymmetricProps): string => {
const decryptAssymmetric = ({
ciphertext,
nonce,
publicKey,
privateKey
}: decryptAsymmetricProps): string => {
const plaintext = nacl.box.open(
nacl.util.decodeBase64(ciphertext),
nacl.util.decodeBase64(nonce),
@@ -65,7 +73,7 @@ const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }: decryp
type encryptSymmetricProps = {
plaintext: string;
key: string;
}
};
/**
* Return symmetrically encrypted [plaintext] using [key].
@@ -73,15 +81,18 @@ type encryptSymmetricProps = {
* @param {String} obj.plaintext - plaintext to encrypt
* @param {String} obj.key - 16-byte hex key
*/
const encryptSymmetric = ({ plaintext, key }: encryptSymmetricProps): object => {
const encryptSymmetric = ({
plaintext,
key
}: encryptSymmetricProps): object => {
let ciphertext, iv, tag;
try {
const obj = aes.encrypt(plaintext, key);
const obj = aes.encrypt({ text: plaintext, secret: key });
ciphertext = obj.ciphertext;
iv = obj.iv;
tag = obj.tag;
} catch (err) {
console.log("Failed to perform encryption");
console.log('Failed to perform encryption');
console.log(err);
process.exit(1);
}
@@ -89,7 +100,7 @@ const encryptSymmetric = ({ plaintext, key }: encryptSymmetricProps): object =>
return {
ciphertext,
iv,
tag,
tag
};
};
@@ -98,7 +109,7 @@ type decryptSymmetricProps = {
iv: string;
tag: string;
key: string;
}
};
/**
* Return symmetrically decypted [ciphertext] using [iv], [tag],
@@ -110,12 +121,17 @@ type decryptSymmetricProps = {
* @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;
try {
plaintext = aes.decrypt(ciphertext, iv, tag, key);
plaintext = aes.decrypt({ ciphertext, iv, tag, secret: key });
} catch (err) {
console.log("Failed to perform decryption");
console.log('Failed to perform decryption');
process.exit(1);
}
@@ -126,5 +142,5 @@ export {
decryptAssymmetric,
decryptSymmetric,
encryptAssymmetric,
encryptSymmetric,
encryptSymmetric
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -6,21 +6,21 @@ const LINE =
* @param {Buffer} src - source buffer
* @returns {String} text - text of buffer
*/
function parse(src) {
const obj = {};
function parse(src: Buffer) {
const obj: Record<string, string> = {};
// Convert buffer to string
let lines = src.toString();
// Convert line breaks to same format
lines = lines.replace(/\r\n?/gm, "\n");
lines = lines.replace(/\r\n?/gm, '\n');
let match;
while ((match = LINE.exec(lines)) != null) {
const key = match[1];
// Default undefined or null to empty string
let value = match[2] || "";
let value = match[2] || '';
// Remove whitespace
value = value.trim();
@@ -29,12 +29,12 @@ function parse(src) {
const maybeQuote = value[0];
// Remove surrounding quotes
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, '$2');
// Expand newlines if double quoted
if (maybeQuote === '"') {
value = value.replace(/\\n/g, "\n");
value = value.replace(/\\r/g, "\r");
value = value.replace(/\\n/g, '\n');
value = value.replace(/\\r/g, '\r');
}
// Add to object

View File

@@ -3,19 +3,19 @@
* @returns
*/
const guidGenerator = () => {
var S4 = function () {
const S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return (
S4() +
S4() +
"-" +
'-' +
S4() +
"-" +
'-' +
S4() +
"-" +
'-' +
S4() +
"-" +
'-' +
S4() +
S4() +
S4()

View File

@@ -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 guidGenerator from "../randomId";
import { envMapping } from '../../../public/data/frequentConstants';
import guidGenerator from '../randomId';
const {
decryptAssymmetric,
decryptSymmetric,
} = require("../cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
decryptSymmetric
} = require('../cryptography/crypto');
const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');
interface Props {
env: keyof typeof envMapping;
setFileState: any;
setIsKeyAvailable: any;
setData: any;
workspaceId: string;
}
const getSecretsForProject = async ({
env,
setFileState,
setIsKeyAvailable,
setData,
workspaceId,
}) => {
workspaceId
}: Props) => {
try {
let file;
try {
@@ -24,44 +32,42 @@ const getSecretsForProject = async ({
setFileState(file);
} 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
setIsKeyAvailable(
!file.key ? (file.secrets.length == 0 ? true : false) : true
);
setIsKeyAvailable(!file.key ? file.secrets.length == 0 : 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) {
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: file.key.encryptedKey,
nonce: file.key.nonce,
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
const plainTextKey = decryptSymmetric({
ciphertext: secretPair.secretKey.ciphertext,
iv: secretPair.secretKey.iv,
tag: secretPair.secretKey.tag,
key,
key
});
const plainTextValue = decryptSymmetric({
ciphertext: secretPair.secretValue.ciphertext,
iv: secretPair.secretValue.iv,
tag: secretPair.secretValue.tag,
key,
key
});
tempFileState.push({
key: plainTextKey,
value: plainTextValue,
type: secretPair.type,
type: secretPair.type
});
});
}
@@ -72,9 +78,9 @@ const getSecretsForProject = async ({
return {
id: guidGenerator(),
pos: index,
key: line["key"],
value: line["value"],
type: line["type"]
key: line['key'],
value: line['value'],
type: line['type']
}
})
);
@@ -82,12 +88,12 @@ const getSecretsForProject = async ({
return tempFileState.map((line, index) => [
guidGenerator(),
index,
line["key"],
line["value"],
line["type"],
line['key'],
line['value'],
line['type']
]);
} catch (error) {
console.log("Something went wrong during accessing or decripting secrets.");
console.log('Something went wrong during accessing or decripting secrets.');
}
return true;
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -29,7 +29,7 @@
"markdown-it": "^13.0.1",
"next": "^12.2.5",
"posthog-js": "^1.34.0",
"query-string": "^7.1.1",
"query-string": "^7.1.3",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1",
"react-code-input": "^3.10.1",
@@ -2467,9 +2467,9 @@
}
},
"node_modules/decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"engines": {
"node": ">=0.10"
}
@@ -5826,11 +5826,11 @@
}
},
"node_modules/query-string": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"dependencies": {
"decode-uri-component": "^0.2.0",
"decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
@@ -9126,9 +9126,9 @@
}
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og=="
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
},
"deep-is": {
"version": "0.1.4",
@@ -11477,11 +11477,11 @@
"dev": true
},
"query-string": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"requires": {
"decode-uri-component": "^0.2.0",
"decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"

View File

@@ -32,7 +32,7 @@
"markdown-it": "^13.0.1",
"next": "^12.2.5",
"posthog-js": "^1.34.0",
"query-string": "^7.1.1",
"query-string": "^7.1.3",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1",
"react-code-input": "^3.10.1",

View File

@@ -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)
@@ -11,12 +20,12 @@ const changePassword2 = ({
tag,
salt,
verifier,
clientProof,
}) => {
return SecurityClient.fetchCall("/api/v1/password/change-password", {
method: "POST",
clientProof
}: Props) => {
return SecurityClient.fetchCall('/api/v1/password/change-password', {
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json'
},
body: JSON.stringify({
clientProof: clientProof,
@@ -24,13 +33,13 @@ const changePassword2 = ({
iv: iv,
tag: tag,
salt: salt,
verifier: verifier,
}),
verifier: verifier
})
}).then(async (res) => {
if (res.status == 200) {
if (res && res.status == 200) {
return res;
} else {
console.log("Failed to change the password");
console.log('Failed to change the password');
}
});
};

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;

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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

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.
* We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry
*/
const logout = async () => {
return SecurityClient.fetchCall("/api/v1/auth/logout", {
method: "POST",
return SecurityClient.fetchCall('/api/v1/auth/logout', {
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json'
},
credentials: "include",
credentials: 'include'
}).then((res) => {
if (res?.status == 200) {
SecurityClient.setToken("");
SecurityClient.setToken('');
// Delete the cookie by not setting a value; Alternatively clear the local storage
localStorage.setItem("publicKey", "");
localStorage.setItem("encryptedPrivateKey", "");
localStorage.setItem("iv", "");
localStorage.setItem("tag", "");
localStorage.setItem("PRIVATE_KEY", "");
console.log("User logged out", res);
localStorage.setItem('publicKey', '');
localStorage.setItem('encryptedPrivateKey', '');
localStorage.setItem('iv', '');
localStorage.setItem('tag', '');
localStorage.setItem('PRIVATE_KEY', '');
console.log('User logged out', res);
return res;
} else {
console.log("Failed to log out");
console.log('Failed to log out');
}
});
};

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;

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;

View File

@@ -2,15 +2,15 @@
* This route send the verification email to the user's email (contains a 6-digit verification code)
* @param {*} email
*/
const sendVerificationEmail = (email) => {
fetch("/api/v1/signup/email/signup", {
method: "POST",
const sendVerificationEmail = (email: string) => {
fetch('/api/v1/signup/email/signup', {
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
}),
email: email
})
});
};

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;

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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,10 @@
const publicKeyInfisical = () => {
return fetch('/api/v1/key/publicKey/infisical', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
};
export default publicKeyInfisical;

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;

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;

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;

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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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