Merge branch 'main' into ph-telemetry

This commit is contained in:
mv-turtle
2023-01-09 13:26:55 -08:00
committed by GitHub
42 changed files with 6035 additions and 2913 deletions

View File

@ -10,6 +10,9 @@ jobs:
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: 🔧 Set up QEMU
uses: docker/setup-qemu-action@v2
- name: 🔧 Set up Docker Buildx
@ -39,7 +42,8 @@ jobs:
with:
push: true
context: backend
tags: infisical/backend:latest
tags: infisical/backend:${{ steps.commit.outputs.short }},
infisical/backend:latest
platforms: linux/amd64,linux/arm64
frontend-image:
@ -49,6 +53,9 @@ jobs:
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: 🔧 Set up QEMU
uses: docker/setup-qemu-action@v2
- name: 🔧 Set up Docker Buildx
@ -80,7 +87,8 @@ jobs:
with:
push: true
context: frontend
tags: infisical/frontend:latest
tags: infisical/frontend:${{ steps.commit.outputs.short }},
infisical/frontend:latest
platforms: linux/amd64,linux/arm64
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}

View File

@ -333,7 +333,7 @@ Infisical officially launched as v.1.0 on November 21st, 2022. There are a lot o
<!-- 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/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?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/jon4hz"><img src="https://avatars.githubusercontent.com/u/26183582?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/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?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/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?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/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?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/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?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/JoaoVictor6"><img src="https://avatars.githubusercontent.com/u/68869379?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mocherfaoui"><img src="https://avatars.githubusercontent.com/u/37941426?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jon4hz"><img src="https://avatars.githubusercontent.com/u/26183582?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/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?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/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?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/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?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>
## 🌎 Translations

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,8 @@
"query-string": "^7.1.3",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",
"swagger-autogen": "^2.22.0",
"swagger-ui-express": "^4.6.0",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3",
@ -4549,7 +4551,6 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -6690,7 +6691,6 @@
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
},
@ -11174,6 +11174,47 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swagger-autogen": {
"version": "2.22.0",
"resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.22.0.tgz",
"integrity": "sha512-MPdtwgx/RL3og0RjFVV9hPoQv3x+c3ZRhS0Vjp9k94DLV7iUgIuCg8H+uAT8oD5w48ATTRT1VjcOHlCGH62pdA==",
"dependencies": {
"acorn": "^7.4.1",
"deepmerge": "^4.2.2",
"glob": "^7.1.7",
"json5": "^2.2.1"
}
},
"node_modules/swagger-autogen/node_modules/acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/swagger-ui-dist": {
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz",
"integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA=="
},
"node_modules/swagger-ui-express": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.0.tgz",
"integrity": "sha512-ZxpQFp1JR2RF8Ar++CyJzEDdvufa08ujNUJgMVTMWPi86CuQeVdBtvaeO/ysrz6dJAYXf9kbVNhWD7JWocwqsA==",
"dependencies": {
"swagger-ui-dist": ">=4.11.0"
},
"engines": {
"node": ">= v0.10.32"
},
"peerDependencies": {
"express": ">=4.0.0"
}
},
"node_modules/tar": {
"version": "6.1.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz",
@ -15582,8 +15623,7 @@
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
},
"delayed-stream": {
"version": "1.0.0",
@ -17200,8 +17240,7 @@
"json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
},
"jsonwebtoken": {
"version": "9.0.0",
@ -20424,6 +20463,37 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
"swagger-autogen": {
"version": "2.22.0",
"resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.22.0.tgz",
"integrity": "sha512-MPdtwgx/RL3og0RjFVV9hPoQv3x+c3ZRhS0Vjp9k94DLV7iUgIuCg8H+uAT8oD5w48ATTRT1VjcOHlCGH62pdA==",
"requires": {
"acorn": "^7.4.1",
"deepmerge": "^4.2.2",
"glob": "^7.1.7",
"json5": "^2.2.1"
},
"dependencies": {
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
}
}
},
"swagger-ui-dist": {
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz",
"integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA=="
},
"swagger-ui-express": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.0.tgz",
"integrity": "sha512-ZxpQFp1JR2RF8Ar++CyJzEDdvufa08ujNUJgMVTMWPi86CuQeVdBtvaeO/ysrz6dJAYXf9kbVNhWD7JWocwqsA==",
"requires": {
"swagger-ui-dist": ">=4.11.0"
}
},
"tar": {
"version": "6.1.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz",

View File

@ -1,46 +1,11 @@
{
"dependencies": {
"@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",
"await-to-js": "^3.0.0",
"axios": "^1.1.3",
"bcrypt": "^5.1.0",
"bigint-conversion": "^2.2.2",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"express-rate-limit": "^6.7.0",
"express-validator": "^6.14.2",
"handlebars": "^4.7.7",
"helmet": "^5.1.1",
"jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10",
"mongoose": "^6.7.2",
"nodemailer": "^6.8.0",
"posthog-node": "^2.2.2",
"query-string": "^7.1.3",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3",
"utility-types": "^3.10.0",
"winston": "^3.8.2",
"winston-loki": "^6.0.6"
},
"name": "infisical-api",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "npm run build && node build/index.js",
"dev": "nodemon",
"swagger-autogen": "node ./swagger.ts",
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build",
"lint": "eslint . --ext .ts",
"lint-and-fix": "eslint . --ext .ts --fix",
@ -108,5 +73,43 @@
"suiteNameTemplate": "{filepath}",
"classNameTemplate": "{classname}",
"titleTemplate": "{title}"
},
"dependencies": {
"@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",
"await-to-js": "^3.0.0",
"axios": "^1.1.3",
"bcrypt": "^5.1.0",
"bigint-conversion": "^2.2.2",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"express-rate-limit": "^6.7.0",
"express-validator": "^6.14.2",
"handlebars": "^4.7.7",
"helmet": "^5.1.1",
"jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10",
"mongoose": "^6.7.2",
"nodemailer": "^6.8.0",
"posthog-node": "^2.2.2",
"query-string": "^7.1.3",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",
"swagger-autogen": "^2.22.0",
"swagger-ui-express": "^4.6.0",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3",
"utility-types": "^3.10.0",
"winston": "^3.8.2",
"winston-loki": "^6.0.6"
}
}

View File

@ -6,6 +6,10 @@ import helmet from 'helmet';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import dotenv from 'dotenv';
import swaggerUi = require('swagger-ui-express');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const swaggerFile = require('../api-documentation.json')
dotenv.config();
import { PORT, NODE_ENV, SITE_URL } from './config';
@ -105,6 +109,8 @@ app.use('/api/v2/secrets', v2SecretsRouter);
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
app.use('/api/v2/api-key-data', v2APIKeyDataRouter);
// api docs
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
// Server status
app.use('/api', healthCheck)

22
backend/swagger.ts Normal file
View File

@ -0,0 +1,22 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' });
const doc = {
info: {
title: 'Infisical API',
description: 'List of all available APIs that can be consumed',
},
host: ['https://infisical.com'],
securityDefinitions: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
};
const outputFile = './api-documentation.json';
const endpointsFiles = ['./src/app.ts'];
swaggerAutogen(outputFile, endpointsFiles, doc);

View File

@ -28,6 +28,8 @@ services:
- ./backend/src:/app/src
- ./backend/nodemon.json:/app/nodemon.json
- /app/node_modules
- ./backend/api-documentation.json:/app/api-documentation.json
- ./backend/swagger.ts:/app/swagger.ts
command: npm run dev
env_file: .env
environment:

View File

@ -39,6 +39,11 @@
"icon": "server",
"url": "self-hosting"
},
{
"name": "API Reference",
"icon": "cloud",
"url": "self-hosting"
},
{
"name": "Integrations",
"icon": "plug",

View File

@ -4,13 +4,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function Error({ text }: { text: string }): JSX.Element {
return (
<div className="relative bg-red-500 opacity-100 border flex flex-row justify-center m-auto items-center w-fit rounded-full mb-4">
<div className="relative flex flex-row justify-center m-auto items-center w-fit rounded-full">
<FontAwesomeIcon
icon={faExclamationTriangle}
className="text-white mt-1.5 mb-2 mx-2"
className="text-red mt-1.5 mb-2 mx-2"
/>
{text && (
<p className="relative top-0 text-white mr-2 text-sm py-1">{text}</p>
<p className="relative top-0 text-red mr-2 text-sm py-1">{text}</p>
)}
</div>
);

View File

@ -55,7 +55,7 @@ export default function EventFilter({
{selected != '' ? (
<p className="select-none text-bunker-100">{t("activity:event." + selected)}</p>
) : (
<p className="select-none">Select an event</p>
<p className="select-none">{String(t("common:select-event"))}</p>
)}
{selected != '' ? (
<FontAwesomeIcon

View File

@ -11,7 +11,6 @@ import {
faKey,
faMobile,
faPlug,
faTimeline,
faUser,
} from "@fortawesome/free-solid-svg-icons";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
@ -184,6 +183,7 @@ export default function Layout({ children }: LayoutProps) {
if (
userWorkspaces.length == 0 &&
router.asPath != "/noprojects" &&
!router.asPath.includes("home")&&
!router.asPath.includes("settings")
) {
router.push("/noprojects");

View File

@ -109,7 +109,7 @@ const AddProjectMemberDialog = ({
selected={email ? email : data[0]}
onChange={setEmail}
data={data}
width="full"
isFull={true}
/>
)}
</div>

View File

@ -0,0 +1,77 @@
import { Fragment } from "react";
import { useTranslation } from "react-i18next";
import { Dialog, Transition } from "@headlessui/react";
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
type Props = {
isOpen: boolean
onClose: () => void
onSubmit: () => void
}
export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
const { t } = useTranslation()
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={() => {}}>
<div className="fixed inset-0 overflow-y-auto">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" onClick={onClose} />
</Transition.Child>
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
{t('dashboard:sidebar.delete-key-dialog.title')}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
{t('dashboard:sidebar.delete-key-dialog.confirm-delete-message')}
</p>
</div>
<div className="mt-6 flex justify-start">
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-red-700 hover:bg-red-600 px-4 py-2 text-sm font-medium text-bunker-200 hover:text-white text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onSubmit}
>
Delete
</button>
<button
type="button"
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-bunker-500 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-mineshaft-500 hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onClose}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};

View File

@ -117,13 +117,13 @@ const UserTable = ({
return (
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
<div className="absolute rounded-t-md w-full h-14 bg-white/5"></div>
<table className="w-full my-1">
<thead className="text-gray-400">
<div className="absolute rounded-t-md w-full h-[3.25rem] bg-white/5"></div>
<table className="w-full my-0.5">
<thead className="text-gray-400 text-sm font-light">
<tr>
<th className="text-left pl-6 py-3.5">First Name</th>
<th className="text-left pl-6 py-3.5">Last Name</th>
<th className="text-left pl-6 py-3.5">Email</th>
<th className="text-left pl-6 py-3.5">FIRST NAME</th>
<th className="text-left pl-6 py-3.5">LAST NAME</th>
<th className="text-left pl-6 py-3.5">EMAIL</th>
<th></th>
</tr>
</thead>

View File

@ -0,0 +1,33 @@
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next';
import Button from '../basic/buttons/Button';
import { DeleteEnvVar } from '../basic/dialog/DeleteEnvVar';
type Props = {
onSubmit: () => void
}
export function DeleteActionButton({ onSubmit }: Props) {
const { t } = useTranslation();
const [open, setOpen] = useState(false)
return (
<div className="bg-[#9B3535] opacity-70 hover:opacity-100 w-[4.5rem] h-[2.5rem] rounded-md duration-200 ml-2">
<Button
text={String(t("Delete"))}
// onButtonPressed={onSubmit}
color="red"
size="md"
onButtonPressed={() => setOpen(true)}
/>
<DeleteEnvVar
isOpen={open}
onClose={() => {
setOpen(false)
}}
onSubmit={onSubmit}
/>
</div>
)
}

View File

@ -3,10 +3,11 @@ import Image from "next/image";
import { useTranslation } from "next-i18next";
import { faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { parseDocument, Scalar, YAMLMap } from 'yaml';
import Button from "../basic/buttons/Button";
import Error from "../basic/Error";
import parse from "../utilities/file";
import { parseDotEnv } from '../utilities/parseDotEnv';
import guidGenerator from "../utilities/randomId";
interface DropZoneProps {
@ -51,6 +52,53 @@ const DropZone = ({
const [loading, setLoading] = useState(false);
const getSecrets = (file: ArrayBuffer, fileType: string) => {
let secrets;
switch (fileType) {
case 'env': {
const keyPairs = parseDotEnv(file);
secrets = Object.keys(keyPairs).map((key, index) => {
return {
id: guidGenerator(),
pos: numCurrentRows + index,
key: key,
value: keyPairs[key as keyof typeof keyPairs].value,
comment: keyPairs[key as keyof typeof keyPairs].comments.join('\n'),
type: 'shared',
};
});
break;
}
case 'yml': {
const parsedFile = parseDocument(file.toString());
const keyPairs = parsedFile.contents!.toJSON();
secrets = Object.keys(keyPairs).map((key, index) => {
const fileContent = parsedFile.contents as YAMLMap<Scalar, Scalar>;
const comment =
fileContent!.items
.find((item) => item.key.value === key)
?.key?.commentBefore?.split('\n')
.map((comment) => comment.trim())
.join('\n') ?? '';
return {
id: guidGenerator(),
pos: numCurrentRows + index,
key: key,
value: keyPairs[key as keyof typeof keyPairs]?.toString() ?? '',
comment,
type: 'shared',
};
});
break;
}
default:
secrets = '';
break;
}
return secrets;
};
// This function function immediately parses the file after it is dropped
const handleDrop = async (e: DragEvent) => {
setLoading(true);
@ -61,20 +109,12 @@ const DropZone = ({
const file = e.dataTransfer.files[0];
const reader = new FileReader();
const fileType = file.name.split('.')[1];
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
// parse function's argument looks like to be ArrayBuffer
const keyPairs = parse(event.target.result as Buffer);
const newData = Object.keys(keyPairs).map((key, index) => {
return {
id: guidGenerator(),
pos: numCurrentRows + index,
key: key,
value: keyPairs[key as keyof typeof keyPairs],
type: "shared",
};
});
const newData = getSecrets(event.target.result as ArrayBuffer, fileType);
setData(newData);
setButtonReady(true);
};
@ -95,25 +135,14 @@ const DropZone = ({
setTimeout(() => setLoading(false), 5000);
if (e.currentTarget.files === null) return;
const file = e.currentTarget.files[0];
const fileType = file.name.split('.')[1];
const reader = new FileReader();
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
const { result } = event.target;
if (typeof result === "string") {
const newData = result
.split("\n")
.map((line: string, index: number) => {
return {
id: guidGenerator(),
pos: numCurrentRows + index,
key: line.split("=")[0],
value: line.split("=").slice(1, line.split("=").length).join("="),
type: "shared",
};
});
setData(newData);
setButtonReady(true);
}
const newData = getSecrets(result as ArrayBuffer, fileType);
setData(newData);
setButtonReady(true);
};
reader.readAsText(file);
};
@ -139,7 +168,7 @@ const DropZone = ({
id="fileSelect"
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
accept=".txt,.env,.yml"
onChange={handleFileSelect}
/>
{errorDragAndDrop ? (
@ -176,7 +205,7 @@ const DropZone = ({
id="fileSelect"
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
accept=".txt,.env,.yml"
onChange={handleFileSelect}
/>
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
@ -187,7 +216,7 @@ const DropZone = ({
<div className="z-10 mb-6">
<Button
color="mineshaft"
text="Create a new .env file"
text={String(t("dashboard:add-secret"))}
onButtonPressed={createNewFile}
size="md"
/>

View File

@ -9,6 +9,7 @@ import Button from '../basic/buttons/Button';
import Toggle from '../basic/Toggle';
import CommentField from './CommentField';
import DashboardInputField from './DashboardInputField';
import { DeleteActionButton } from './DeleteActionButton';
import GenerateSecretMenu from './GenerateSecretMenu';
@ -28,6 +29,10 @@ interface OverrideProps {
pos: number;
comment: string;
}
export interface DeleteRowFunctionProps {
ids: string[];
secretName: string;
}
interface SideBarProps {
toggleSidebar: (value: string) => void;
@ -41,7 +46,7 @@ interface SideBarProps {
savePush: () => void;
sharedToHide: string[];
setSharedToHide: (values: string[]) => void;
deleteRow: any;
deleteRow: (props: DeleteRowFunctionProps) => void;
}
/**
@ -170,14 +175,9 @@ const SideBar = ({
active={buttonReady}
textDisabled="Saved"
/>
<div className="bg-[#9B3535] opacity-70 hover:opacity-100 w-[4.5rem] h-[2.5rem] rounded-md duration-200 ml-2">
<Button
text={String(t("Delete"))}
onButtonPressed={() => deleteRow({ ids: overrideEnabled ? data.map(secret => secret.id) : [data.filter(secret => secret.type == "shared")[0]?.id], secretName: data[0]?.key })}
color="red"
size="md"
/>
</div>
<DeleteActionButton
onSubmit={() => deleteRow({ ids: overrideEnabled ? data.map(secret => secret.id) : [data.filter(secret => secret.type == "shared")[0]?.id], secretName: data[0]?.key })}
/>
</div>
</div>
};

View File

@ -13,6 +13,7 @@ import {
faGear,
faPlus,
faRightFromBracket,
faUpRightFromSquare,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Menu, Transition } from "@headlessui/react";
@ -107,6 +108,14 @@ export default function Navbar() {
</div>
</div>
<div className="relative flex justify-start items-center mx-2 z-40">
<a
href="https://infisical.com/docs/getting-started/introduction"
target="_blank"
rel="noopener noreferrer"
className="text-gray-200 hover:text-primary duration-200">
Docs
<FontAwesomeIcon icon={faUpRightFromSquare} className="text-xs mb-[0.1rem] mr-5 ml-1.5" />
</a>
<Menu as="div" className="relative inline-block text-left">
<div className="mr-4">
<Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">

View File

@ -0,0 +1,136 @@
import React, { useState } from "react";
import ReactCodeInput from "react-code-input";
import { useTranslation } from "next-i18next";
import sendVerificationEmail from "~/pages/api/auth/SendVerificationEmail";
import Button from "../basic/buttons/Button";
import Error from "../basic/Error";
// The style for the verification code input
const props = {
inputStyle: {
fontFamily: "monospace",
margin: "4px",
MozAppearance: "textfield",
width: "55px",
borderRadius: "5px",
fontSize: "24px",
height: "55px",
paddingLeft: "7",
backgroundColor: "#0d1117",
color: "white",
border: "1px solid #2d2f33",
textAlign: "center",
outlineColor: "#8ca542",
borderColor: "#2d2f33"
},
} as const;
const propsPhone = {
inputStyle: {
fontFamily: "monospace",
margin: "4px",
MozAppearance: "textfield",
width: "40px",
borderRadius: "5px",
fontSize: "24px",
height: "40px",
paddingLeft: "7",
backgroundColor: "#0d1117",
color: "white",
border: "1px solid #2d2f33",
textAlign: "center",
outlineColor: "#8ca542",
borderColor: "#2d2f33"
},
} as const;
interface CodeInputStepProps {
email: string;
incrementStep: () => void;
setCode: (value: string) => void;
codeError: boolean;
}
/**
* This is the second step of sign up where users need to verify their email
* @param {object} obj
* @param {string} obj.email - user's email to which we just sent a verification email
* @param {function} obj.incrementStep - goes to the next step of signup
* @param {function} obj.setCode - state updating function that set the current value of the emai verification code
* @param {boolean} obj.codeError - whether the code was inputted wrong or now
* @returns
*/
export default function CodeInputStep({ email, incrementStep, setCode, codeError }: CodeInputStepProps): JSX.Element {
const [isLoading, setIsLoading] = useState(false);
const [isResendingVerificationEmail, setIsResendingVerificationEmail] =
useState(false);
const { t } = useTranslation();
const resendVerificationEmail = async () => {
setIsResendingVerificationEmail(true);
setIsLoading(true);
sendVerificationEmail(email);
setTimeout(() => {
setIsLoading(false);
setIsResendingVerificationEmail(false);
}, 2000);
};
return (
<div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
<p className="text-l flex justify-center text-bunker-300">
{"We've"} sent a verification email to{" "}
</p>
<p className="text-l flex justify-center font-semibold my-2 text-bunker-300">
{email}{" "}
</p>
<div className="hidden md:block">
<ReactCodeInput
name=""
inputMode="tel"
type="text"
fields={6}
onChange={setCode}
{...props}
className="mt-6 mb-2"
/>
</div>
<div className="block md:hidden">
<ReactCodeInput
name=""
inputMode="tel"
type="text"
fields={6}
onChange={setCode}
{...propsPhone}
className="mt-2 mb-6"
/>
</div>
{codeError && <Error text={t("signup:step2-code-error")} />}
<div className="flex max-w-max min-w-28 flex-col items-center justify-center md:p-2 max-h-24 mx-auto text-lg px-4 mt-4 mb-2">
<Button
text={t("signup:verify") ?? ""}
onButtonPressed={incrementStep}
size="lg"
/>
</div>
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
<div className="flex flex-row items-baseline gap-1 text-sm">
<span className="text-bunker-400">
Not seeing an email?
</span>
<u className={`font-normal ${isResendingVerificationEmail ? 'text-bunker-400' : 'text-primary-700 hover:text-primary duration-200'}`}>
<button disabled={isLoading} onClick={resendVerificationEmail}>
{isResendingVerificationEmail ? "Resending..." : "Resend"}
</button>
</u>
</div>
<p className="text-sm text-bunker-400 pb-2">
{t("signup:step2-spam-alert")}
</p>
</div>
</div>
);
}

View File

@ -0,0 +1,60 @@
import React from "react";
import { useTranslation } from "next-i18next";
import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "../basic/buttons/Button";
import issueBackupKey from "../utilities/cryptography/issueBackupKey";
interface DownloadBackupPDFStepProps {
incrementStep: () => void;
email: string;
password: string;
name: string;
}
/**
* This is the step of the signup flow where the user downloads the backup pdf
* @param {object} obj
* @param {function} obj.incrementStep - function that moves the user on to the next stage of signup
* @param {string} obj.email - user's email
* @param {string} obj.password - user's password
* @param {string} obj.name - user's name
* @returns
*/
export default function DonwloadBackupPDFStep({ incrementStep, email, password, name }: DownloadBackupPDFStepProps): JSX.Element {
const { t } = useTranslation();
return (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
{t("signup:step4-message")}
</p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
<div>{t("signup:step4-description1")}</div>
<div className="mt-3">{t("signup:step4-description2")}</div>
</div>
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
{t("signup:step4-description3")}
</div>
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
<Button
text="Download PDF"
onButtonPressed={async () => {
await issueBackupKey({
email,
password,
personalName: name,
setBackupKeyError: (value: boolean) => {},
setBackupKeyIssued: (value: boolean) => {},
});
incrementStep();
}}
size="lg"
/>
</div>
</div>
);
}

View File

@ -0,0 +1,97 @@
import React, { useState } from "react";
import Link from "next/link";
import { useTranslation } from "next-i18next";
import sendVerificationEmail from "~/pages/api/auth/SendVerificationEmail";
import Button from "../basic/buttons/Button";
import InputField from "../basic/InputField";
interface DownloadBackupPDFStepProps {
incrementStep: () => void;
email: string;
setEmail: (value: string) => void;
}
/**
* This is the first step of the sign up process - users need to enter their email
* @param {object} obj
* @param {string} obj.email - email of a user signing up
* @param {function} obj.setEmail - funciton that manages the state of the email variable
* @param {function} obj.incrementStep - function to go to the next step of the signup flow
* @returns
*/
export default function EnterEmailStep({ email, setEmail, incrementStep }: DownloadBackupPDFStepProps): JSX.Element {
const [emailError, setEmailError] = useState(false);
const [emailErrorMessage, setEmailErrorMessage] = useState("");
const { t } = useTranslation();
/**
* Verifies if the entered email "looks" correct
*/
const emailCheck = () => {
let emailCheckBool = false;
if (!email) {
setEmailError(true);
setEmailErrorMessage("Please enter your email.");
emailCheckBool = true;
} else if (
!email.includes("@") ||
!email.includes(".") ||
!/[a-z]/.test(email)
) {
setEmailError(true);
setEmailErrorMessage("Please enter a valid email.");
emailCheckBool = true;
} else {
setEmailError(false);
}
// If everything is correct, go to the next step
if (!emailCheckBool) {
sendVerificationEmail(email);
incrementStep();
}
};
return (
<div>
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-8 md:px-6 mx-1 rounded-xl drop-shadow-xl">
<p className="text-4xl font-semibold flex justify-center text-primary">
{'Let\''}s get started
</p>
<div className="flex items-center justify-center w-5/6 md:w-full m-auto md:p-2 rounded-lg max-h-24 mt-4">
<InputField
label="Email"
onChangeHandler={setEmail}
type="email"
value={email}
placeholder=""
isRequired
error={emailError}
errorText={emailErrorMessage}
autoComplete="username"
/>
</div>
<div className="flex flex-col items-center justify-center w-5/6 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-4 md:mt-4 text-sm text-center md:text-left">
<p className="text-gray-400 mt-2 md:mx-0.5">
{t("signup:step1-privacy")}
</p>
<div className="text-l mt-6 m-2 md:m-8 px-8 py-1 text-lg">
<Button text="Get Started" type="submit" onButtonPressed={emailCheck} size="lg" />
</div>
</div>
</div>
<div className="flex flex-col items-center justify-center w-full md:pb-2 max-w-md mx-auto pt-2 mb-48 md:mb-16 mt-2">
<Link href="/login">
<button type="button" className="w-max pb-3 hover:opacity-90 duration-200">
<u className="font-normal text-sm text-primary-500">
{t("signup:already-have-account")}
</u>
</button>
</Link>
</div>
</div>
);
}

View File

@ -0,0 +1,72 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import addUserToOrg from "~/pages/api/organization/addUserToOrg";
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
import Button from "../basic/buttons/Button";
/**
* This is the last step of the signup flow. People can optionally invite their teammates here.
*/
export default function TeamInviteStep(): JSX.Element {
const [emails, setEmails] = useState("");
const { t } = useTranslation();
const router = useRouter();
// Redirect user to the getting started page
const redirectToHome = async () => {
const userWorkspaces = await getWorkspaces();
const userWorkspace = userWorkspaces[0]._id;
router.push("/home/" + userWorkspace);
}
const inviteUsers = async ({ emails }: { emails: string; }) => {
emails
.split(',')
.map(email => email.trim())
.map(async (email) => await addUserToOrg(email, String(localStorage.getItem('orgData.id'))));
await redirectToHome();
}
return (
<div className="bg-bunker w-max mx-auto h-7/12 pt-6 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-32">
<p className="text-4xl font-semibold flex justify-center text-primary">
{t("signup:step5-invite-team")}
</p>
<p className="text-center flex justify-center text-bunker-300 max-w-xs md:max-w-sm md:mx-8 mb-6 mt-4">
{t("signup:step5-subtitle")}
</p>
<div>
<div className="overflow-auto bg-bunker-800">
<div className="whitespace-pre-wrap break-words bg-transparent">
</div>
</div>
<textarea
className="bg-bunker-800 h-20 w-full placeholder:text-bunker-400 py-1 px-2 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
value={emails}
onChange={(e) => setEmails(e.target.value)}
placeholder="email@example.com, email2@example.com..."
/>
</div>
<div className="flex flex-row max-w-max min-w-28 items-center justify-center md:p-2 max-h-24 mx-auto text-lg px-4 mt-4 mb-2">
<div
className="text-md md:text-sm mx-3 text-bunker-300 bg-mineshaft-700 py-3 md:py-3.5 px-5 rounded-md cursor-pointer hover:bg-mineshaft-500 duration-200"
onClick={redirectToHome}
>
{t("signup:step5-skip")}
</div>
<Button
text={t("signup:step5-send-invites") ?? ""}
onButtonPressed={() => inviteUsers({ emails})}
size="lg"
/>
</div>
</div>
);
}

View File

@ -0,0 +1,306 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import completeAccountInformationSignup from "~/pages/api/auth/CompleteAccountInformationSignup";
import Button from "../basic/buttons/Button";
import InputField from "../basic/InputField";
import attemptLogin from "../utilities/attemptLogin";
import passwordCheck from "../utilities/checks/PasswordCheck";
import Aes256Gcm from "../utilities/cryptography/aes-256-gcm";
const nacl = require("tweetnacl");
const jsrp = require("jsrp");
nacl.util = require("tweetnacl-util");
const client = new jsrp.client();
interface UserInfoStepProps {
verificationToken: string;
incrementStep: () => void;
email: string;
password: string;
setPassword: (value: string) => void;
firstName: string;
setFirstName: (value: string) => void;
lastName: string;
setLastName: (value: string) => void;
}
/**
* This is the step of the sign up flow where people provife their name/surname and password
* @param {object} obj
* @param {string} obj.verificationToken - the token which we use to verify the legitness of a user
* @param {string} obj.incrementStep - a function to move to the next signup step
* @param {string} obj.email - email of a user who is signing up
* @param {string} obj.password - user's password
* @param {string} obj.setPassword - function managing the state of user's password
* @param {string} obj.firstName - user's first name
* @param {string} obj.setFirstName - function managing the state of user's first name
* @param {string} obj.lastName - user's lastName
* @param {string} obj.setLastName - function managing the state of user's last name
*/
export default function UserInfoStep({
verificationToken,
incrementStep,
email,
password,
setPassword,
firstName,
setFirstName,
lastName,
setLastName
}: UserInfoStepProps): JSX.Element {
const [firstNameError, setFirstNameError] = useState(false);
const [lastNameError, setLastNameError] = useState(false);
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const router = useRouter();
// Verifies if the information that the users entered (name, workspace)
// is there, and if the password matches the criteria.
const signupErrorCheck = async () => {
setIsLoading(true);
let errorCheck = false;
if (!firstName) {
setFirstNameError(true);
errorCheck = true;
} else {
setFirstNameError(false);
}
if (!lastName) {
setLastNameError(true);
errorCheck = true;
} else {
setLastNameError(false);
}
errorCheck = passwordCheck({
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck: errorCheck,
});
if (!errorCheck) {
// Generate a random pair of a public and a private key
const pair = nacl.box.keyPair();
const secretKeyUint8Array = pair.secretKey;
const publicKeyUint8Array = pair.publicKey;
const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array);
const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array);
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
text: PRIVATE_KEY,
secret: password
.slice(0, 32)
.padStart(
32 + (password.slice(0, 32).length - new Blob([password]).size),
"0"
),
}) as { ciphertext: string; iv: string; tag: string };
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
client.init(
{
username: email,
password: password,
},
async () => {
client.createVerifier(
async (err: any, result: { salt: string; verifier: string }) => {
const response = await completeAccountInformationSignup({
email,
firstName,
lastName,
organizationName: firstName + "'s organization",
publicKey: PUBLIC_KEY,
ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken,
});
// if everything works, go the main dashboard page.
if (response.status === 200) {
// response = await response.json();
localStorage.setItem("publicKey", PUBLIC_KEY);
localStorage.setItem("encryptedPrivateKey", ciphertext);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
try {
await attemptLogin(
email,
password,
(value: boolean) => {},
router,
true,
false
);
incrementStep();
} catch (error) {
setIsLoading(false);
}
}
}
);
}
);
} else {
setIsLoading(false);
}
};
return (
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-36 md:mb-16">
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
{t("signup:step3-message")}
</p>
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
<InputField
label={t("common:first-name")}
onChangeHandler={setFirstName}
type="name"
value={firstName}
isRequired
errorText={
t("common:validate-required", {
name: t("common:first-name"),
}) as string
}
error={firstNameError}
autoComplete="given-name"
/>
</div>
<div className="mt-2 flex items-center justify-center w-full md:p-2 rounded-lg max-h-24">
<InputField
label={t("common:last-name")}
onChangeHandler={setLastName}
type="name"
value={lastName}
isRequired
errorText={
t("common:validate-required", {
name: t("common:last-name"),
}) as string
}
error={lastNameError}
autoComplete="family-name"
/>
</div>
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
<InputField
label={t("section-password:password")}
onChangeHandler={(password: string) => {
setPassword(password);
passwordCheck({
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck: false,
});
}}
type="password"
value={password}
isRequired
error={
passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase
}
autoComplete="new-password"
id="new-password"
/>
{passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber ? (
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
<div className={`text-gray-400 text-sm mb-1`}>
{t("section-password:validate-base")}
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLength ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
{t("section-password:validate-length")}
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
{t("section-password:validate-case")}
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
{t("section-password:validate-number")}
</div>
</div>
</div>
) : (
<div className="py-2"></div>
)}
</div>
<div className="flex flex-col items-center justify-center md:p-2 max-h-48 max-w-max mx-auto text-lg px-2 py-3">
<Button
text={t("signup:signup") ?? ""}
loading={isLoading}
onButtonPressed={signupErrorCheck}
size="lg"
/>
</div>
</div>
)
}

View File

@ -1,47 +0,0 @@
const LINE =
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
/**
* Return text that is the buffer parsed
* @param {Buffer} src - source buffer
* @returns {String} text - text of buffer
*/
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');
let match;
while ((match = LINE.exec(lines)) != null) {
const key = match[1];
// Default undefined or null to empty string
let value = match[2] || '';
// Remove whitespace
value = value.trim();
// Check if double quoted
const maybeQuote = value[0];
// Remove surrounding quotes
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');
}
// Add to object
obj[key] = value;
}
return obj;
}
export default parse;

View File

@ -0,0 +1,66 @@
const LINE =
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
/**
* Return text that is the buffer parsed
* @param {ArrayBuffer} src - source buffer
* @returns {String} text - text of buffer
*/
export function parseDotEnv(src: ArrayBuffer) {
const object: {
[key: string]: { value: string; comments: string[] };
} = {};
// Convert buffer to string
let lines = src.toString();
// Convert line breaks to same format
lines = lines.replace(/\r\n?/gm, '\n');
let comments: string[] = [];
lines
.split('\n')
.map((line) => {
// collect comments of each env variable
if (line.startsWith('#')) {
comments.push(line.replace('#', '').trim());
} else if (line) {
let match;
let item: [string, string, string[]] | [] = [];
while ((match = LINE.exec(line)) !== null) {
const key = match[1];
// Default undefined or null to empty string
let value = match[2] || '';
// Remove whitespace
value = value.trim();
// Check if double quoted
const maybeQuote = value[0];
// Remove surrounding quotes
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');
}
item = [key, value, comments];
}
comments = [];
return item;
}
return [];
})
.filter((line) => line.length > 1)
.forEach((line) => {
const [key, value, comments] = line;
object[key as string] = { value, comments };
});
return object;
}

View File

@ -29,7 +29,7 @@ class Capturer {
}
class Telemetry {
export default class Telemetry {
constructor() {
if (!Telemetry.instance) {
Telemetry.instance = new Capturer();
@ -40,5 +40,3 @@ class Telemetry {
return Telemetry.instance;
}
}
module.exports = Telemetry;

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import React, { useState } from 'react';
import Image from 'next/image';
import { useTranslation } from "next-i18next";
import {
faAngleDown,
@ -69,23 +69,25 @@ const ActivityLogsRow = ({ row, toggleSidebar }: { row: logData, toggleSidebar:
{payloadOpened &&
<tr className='h-9 text-bunker-200 border-mineshaft-700 border-t text-sm'>
<td></td>
<td>Timestamp</td>
<td>{String(t("common:timestamp"))}</td>
<td>{row.createdAt}</td>
</tr>}
{payloadOpened &&
row.payload?.map((action, index) =>
<tr key={index} className="h-9 text-bunker-200 border-mineshaft-700 border-t text-sm">
<td></td>
<td className="">{t("activity:event." + action.name)}</td>
<td className="text-primary-300 cursor-pointer hover:text-primary duration-200" onClick={() => toggleSidebar(action._id)}>
{action.secretVersions.length + (action.secretVersions.length != 1 ? " secrets" : " secret")}
<FontAwesomeIcon icon={faUpRightFromSquare} className="ml-2 mb-0.5 font-light w-3 h-3"/>
</td>
</tr>)}
row.payload?.map((action, index) => {
action.secretVersions.length > 0 &&
<tr key={index} className="h-9 text-bunker-200 border-mineshaft-700 border-t text-sm">
<td></td>
<td className="">{t("activity:event." + action.name)}</td>
<td className="text-primary-300 cursor-pointer hover:text-primary duration-200" onClick={() => toggleSidebar(action._id)}>
{action.secretVersions.length + (action.secretVersions.length != 1 ? " secrets" : " secret")}
<FontAwesomeIcon icon={faUpRightFromSquare} className="ml-2 mb-0.5 font-light w-3 h-3"/>
</td>
</tr>
})}
{payloadOpened &&
<tr className='h-9 text-bunker-200 border-mineshaft-700 border-t text-sm'>
<td></td>
<td>IP Address</td>
<td>{String(t("common:ip-address"))}</td>
<td>{row.ipAddress}</td>
</tr>}
</>
@ -97,9 +99,12 @@ const ActivityLogsRow = ({ row, toggleSidebar }: { row: logData, toggleSidebar:
* @param {object} obj
* @param {logData} obj.data - data for user activity logs
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
* @param {boolean} obj.isLoading - whether the log data has been loaded yet or not
* @returns
*/
const ActivityTable = ({ data, toggleSidebar }: { data: logData[], toggleSidebar: (value: string) => void; }) => {
const ActivityTable = ({ data, toggleSidebar, isLoading }: { data: logData[], toggleSidebar: (value: string) => void; isLoading: boolean; }) => {
const { t } = useTranslation();
return (
<div className="w-full px-6 mt-8">
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative">
@ -108,10 +113,10 @@ const ActivityTable = ({ data, toggleSidebar }: { data: logData[], toggleSidebar
<thead className="text-bunker-300">
<tr className='text-sm'>
<th className="text-left pl-6 pt-2.5 pb-3"></th>
<th className="text-left font-semibold pt-2.5 pb-3">EVENT</th>
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">USER</th>
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">SOURCE</th>
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">TIME</th>
<th className="text-left font-semibold pt-2.5 pb-3">{String(t("common:event")).toUpperCase()}</th>
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">{String(t("common:user")).toUpperCase()}</th>
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">{String(t("common:source")).toUpperCase()}</th>
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">{String(t("common:time")).toUpperCase()}</th>
<th></th>
</tr>
</thead>
@ -122,6 +127,12 @@ const ActivityTable = ({ data, toggleSidebar }: { data: logData[], toggleSidebar
</tbody>
</table>
</div>
{isLoading && <div className='w-full flex justify-center mb-8 mt-4'><Image
src="/images/loading/loading.gif"
height={60}
width={100}
alt="loading animation"
></Image></div>}
</div>
);
};

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@
"@headlessui/react": "^1.6.6",
"@reduxjs/toolkit": "^1.8.3",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0",
"@stripe/stripe-js": "^1.46.0",
"add": "^2.0.6",
"axios": "^0.27.2",
"axios-auth-refresh": "^3.3.3",
@ -53,7 +53,8 @@
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"uuid": "^8.3.2",
"uuidv4": "^6.2.13"
"uuidv4": "^6.2.13",
"yaml": "^2.2.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.4",

View File

@ -20,7 +20,7 @@ export default function Custom404() {
src="/images/dragon-404.svg"
height={554}
width={942}
alt="google logo"
alt="infisical dragon - page not found"
></Image>
</div>
</div>

View File

@ -51,6 +51,7 @@ export default function Activity() {
const router = useRouter();
const [eventChosen, setEventChosen] = useState('');
const [logsData, setLogsData] = useState<logDataPoint[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [currentOffset, setCurrentOffset] = useState(0);
const currentLimit = 10;
const [currentSidebarAction, toggleSidebar] = useState<string>()
@ -60,6 +61,7 @@ export default function Activity() {
useEffect(() => {
setCurrentOffset(0);
const getLogData = async () => {
setIsLoading(true);
const tempLogsData = await getProjectLogs({ workspaceId: String(router.query.id), offset: 0, limit: currentLimit, userId: "", actionNames: eventChosen })
setLogsData(tempLogsData.map((log: logData) => {
return {
@ -77,6 +79,7 @@ export default function Activity() {
})
}
}))
setIsLoading(false);
}
getLogData();
}, [eventChosen]);
@ -84,6 +87,7 @@ export default function Activity() {
// this use effect adds more data in case 'View More' button is clicked
useEffect(() => {
const getLogData = async () => {
setIsLoading(true);
const tempLogsData = await getProjectLogs({ workspaceId: String(router.query.id), offset: currentOffset, limit: currentLimit, userId: "", actionNames: eventChosen })
setLogsData(logsData.concat(tempLogsData.map((log: logData) => {
return {
@ -101,6 +105,7 @@ export default function Activity() {
})
}
})))
setIsLoading(false);
}
getLogData();
}, [currentLimit, currentOffset]);
@ -115,10 +120,10 @@ export default function Activity() {
{currentSidebarAction && <ActivitySideBar toggleSidebar={toggleSidebar} currentAction={currentSidebarAction} />}
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4 text-bunker-100">Activity Logs</p>
<p className="font-semibold mr-4 text-bunker-100">{t("activity:title")}</p>
</div>
<p className="mr-4 text-base text-gray-400">
Event history for this Infisical project.
{t("activity:subtitle")}
</p>
</div>
<div className="px-6 h-8 mt-2">
@ -130,10 +135,11 @@ export default function Activity() {
<ActivityTable
data={logsData}
toggleSidebar={toggleSidebar}
isLoading={isLoading}
/>
<div className='flex justify-center w-full mb-6'>
<div className='items-center w-60'>
<Button text="View More" textDisabled="End of History" active={logsData.length % 10 == 0 ? true : false} onButtonPressed={loadMoreLogs} size="md" color="mineshaft"/>
<Button text={String(t("common:view-more"))} textDisabled={String(t("common:end-of-history"))} active={logsData.length % 10 == 0 ? true : false} onButtonPressed={loadMoreLogs} size="md" color="mineshaft"/>
</div>
</div>
</div>
@ -142,4 +148,4 @@ export default function Activity() {
Activity.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps(["activity"]);
export const getServerSideProps = getTranslatedServerSideProps(["activity", "common"]);

View File

@ -16,9 +16,11 @@ import {
faPlus,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Menu, Transition } from '@headlessui/react';
import getProjectSercetSnapshotsCount from 'ee/api/secrets/GetProjectSercetSnapshotsCount';
import performSecretRollback from 'ee/api/secrets/PerformSecretRollback';
import PITRecoverySidebar from 'ee/components/PITRecoverySidebar';
import { Document, YAMLSeq } from 'yaml';
import Button from '~/components/basic/buttons/Button';
import ListBox from '~/components/basic/Listbox';
@ -441,7 +443,7 @@ export default function Dashboard() {
setData(sortedData);
};
const deleteCertainRow = ({ ids, secretName }: { ids: string[]; secretName: string; }) => {
deleteRow({ids, secretName});
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import Head from 'next/head';
export default function Activity() {
export default function EmailNotFeriviedPage() {
return (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
<Head>

View File

@ -1,67 +1,24 @@
import { useEffect, useState } from "react";
import ReactCodeInput from "react-code-input";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { faCheck, faWarning, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button";
import Error from "~/components/basic/Error";
import InputField from "~/components/basic/InputField";
import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm";
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
import CodeInputStep from "~/components/signup/CodeInputStep";
import DownloadBackupPDF from "~/components/signup/DonwloadBackupPDFStep";
import EnterEmailStep from "~/components/signup/EnterEmailStep";
import TeamInviteStep from "~/components/signup/TeamInviteStep";
import UserInfoStep from "~/components/signup/UserInfoStep";
import { getTranslatedStaticProps } from "~/components/utilities/withTranslateProps";
import attemptLogin from "~/utilities/attemptLogin";
import passwordCheck from "~/utilities/checks/PasswordCheck";
import checkEmailVerificationCode from "./api/auth/CheckEmailVerificationCode";
import completeAccountInformationSignup from "./api/auth/CompleteAccountInformationSignup";
import sendVerificationEmail from "./api/auth/SendVerificationEmail";
import getWorkspaces from "./api/workspace/getWorkspaces";
// const ReactCodeInput = dynamic(import("react-code-input"));
const nacl = require("tweetnacl");
const jsrp = require("jsrp");
nacl.util = require("tweetnacl-util");
const client = new jsrp.client();
// The stye for the verification code input
const props = {
inputStyle: {
fontFamily: "monospace",
margin: "4px",
MozAppearance: "textfield",
width: "55px",
borderRadius: "5px",
fontSize: "24px",
height: "55px",
paddingLeft: "7",
backgroundColor: "#0d1117",
color: "white",
border: "1px solid gray",
textAlign: "center",
},
} as const;
const propsPhone = {
inputStyle: {
fontFamily: "monospace",
margin: "4px",
MozAppearance: "textfield",
width: "40px",
borderRadius: "5px",
fontSize: "24px",
height: "40px",
paddingLeft: "7",
backgroundColor: "#0d1117",
color: "white",
border: "1px solid gray",
textAlign: "center",
},
} as const;
/**
* @returns the signup page
*/
export default function SignUp() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
@ -69,25 +26,9 @@ export default function SignUp() {
const [lastName, setLastName] = useState("");
const [code, setCode] = useState("");
const [codeError, setCodeError] = useState(false);
const [firstNameError, setFirstNameError] = useState(false);
const [lastNameError, setLastNameError] = useState(false);
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorUpperCase, setPasswordErrorUpperCase] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] =
useState(false);
const [emailError, setEmailError] = useState(false);
const [emailErrorMessage, setEmailErrorMessage] = useState("");
const [step, setStep] = useState(1);
const router = useRouter();
const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isResendingVerificationEmail, setIsResendingVerificationEmail] =
useState(false);
const [backupKeyError, setBackupKeyError] = useState(false);
const [verificationToken, setVerificationToken] = useState("");
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
const { t } = useTranslation();
@ -104,458 +45,28 @@ export default function SignUp() {
}, []);
/**
* Goes to the following step (out of 3) of the signup process.
* Goes to the following step (out of 5) of the signup process.
* Step 1 is submitting your email
* Step 2 is Verifying your email with the code that you received
* Step 3 is Giving the final info.
* Step 3 is asking the final info.
* Step 4 is downloading a backup pdf
* Step 5 is inviting users
*/
const incrementStep = async () => {
if (step == 1) {
setStep(2);
if (step == 1 || step == 3 || step == 4) {
setStep(step + 1);
} else if (step == 2) {
// Checking if the code matches the email.
const response = await checkEmailVerificationCode({ email, code });
if (response.status === 200 || code == "111222") {
if (response.status === 200) {
setVerificationToken((await response.json()).token);
setStep(3);
} else {
setCodeError(true);
}
} else if (step == 3) {
setStep(4);
}
};
/**
* Verifies if the entered email "looks" correct
*/
const emailCheck = () => {
let emailCheckBool = false;
if (!email) {
setEmailError(true);
setEmailErrorMessage("Please enter your email.");
emailCheckBool = true;
} else if (
!email.includes("@") ||
!email.includes(".") ||
!/[a-z]/.test(email)
) {
setEmailError(true);
setEmailErrorMessage("Please enter a valid email.");
emailCheckBool = true;
} else {
setEmailError(false);
}
// If everything is correct, go to the next step
if (!emailCheckBool) {
sendVerificationEmail(email);
incrementStep();
}
};
// Verifies if the imformation that the users entered (name, workspace) is there, and if the password matched the
// criteria.
const signupErrorCheck = async () => {
setIsLoading(true);
let errorCheck = false;
if (!firstName) {
setFirstNameError(true);
errorCheck = true;
} else {
setFirstNameError(false);
}
if (!lastName) {
setLastNameError(true);
errorCheck = true;
} else {
setLastNameError(false);
}
errorCheck = passwordCheck({
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck: errorCheck,
});
if (!errorCheck) {
// Generate a random pair of a public and a private key
const pair = nacl.box.keyPair();
const secretKeyUint8Array = pair.secretKey;
const publicKeyUint8Array = pair.publicKey;
const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array);
const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array);
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
text: PRIVATE_KEY,
secret: password
.slice(0, 32)
.padStart(
32 + (password.slice(0, 32).length - new Blob([password]).size),
"0"
),
}) as { ciphertext: string; iv: string; tag: string };
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
client.init(
{
username: email,
password: password,
},
async () => {
client.createVerifier(
async (err: any, result: { salt: string; verifier: string }) => {
const response = await completeAccountInformationSignup({
email,
firstName,
lastName,
organizationName: firstName + "'s organization",
publicKey: PUBLIC_KEY,
ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken,
});
// if everything works, go the main dashboard page.
if (response.status === 200) {
// response = await response.json();
localStorage.setItem("publicKey", PUBLIC_KEY);
localStorage.setItem("encryptedPrivateKey", ciphertext);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
try {
await attemptLogin(
email,
password,
setErrorLogin,
router,
true,
false
);
incrementStep();
} catch (error) {
setIsLoading(false);
}
}
}
);
}
);
} else {
setIsLoading(false);
}
};
const resendVerificationEmail = async () => {
setIsResendingVerificationEmail(true);
setIsLoading(true);
await sendVerificationEmail(email);
setTimeout(() => {
setIsLoading(false);
setIsResendingVerificationEmail(false);
}, 2000);
};
// Step 1 of the sign up process (enter the email or choose google authentication)
const step1 = (
<div>
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-8 md:px-6 mx-1 rounded-xl drop-shadow-xl">
<p className="text-4xl font-semibold flex justify-center text-primary">
{'Let\''}s get started
</p>
<div className="flex items-center justify-center w-5/6 md:w-full m-auto md:p-2 rounded-lg max-h-24 mt-4">
<InputField
label="Email"
onChangeHandler={setEmail}
type="email"
value={email}
placeholder=""
isRequired
error={emailError}
errorText={emailErrorMessage}
autoComplete="username"
/>
</div>
{/* <div className='flex flex-row justify-left mt-4 max-w-md mx-auto'>
<Checkbox className="mr-4"/>
<p className='text-sm'>I do not want to receive emails about Infisical and its products.</p>
</div> */}
<div className="flex flex-col items-center justify-center w-5/6 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-4 md:mt-4 text-sm text-center md:text-left">
<p className="text-gray-400 mt-2 md:mx-0.5">
{t("signup:step1-privacy")}
</p>
<div className="text-l mt-6 m-2 md:m-8 px-8 py-1 text-lg">
<Button text="Get Started" type="submit" onButtonPressed={emailCheck} size="lg" />
</div>
</div>
</div>
<div className="flex flex-col items-center justify-center w-full md:pb-2 max-w-md mx-auto pt-2 mb-48 md:mb-16 mt-2">
<Link href="/login">
<button type="button" className="w-max pb-3 hover:opacity-90 duration-200">
<u className="font-normal text-sm text-primary-500">
{t("signup:already-have-account")}
</u>
</button>
</Link>
</div>
</div>
);
// Step 2 of the signup process (enter the email verification code)
const step2 = (
<div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
<p className="text-l flex justify-center text-gray-400">
{"We've"} sent a verification email to{" "}
</p>
<p className="text-l flex justify-center font-semibold my-2 text-gray-400">
{email}{" "}
</p>
<div className="hidden md:block">
<ReactCodeInput
name=""
inputMode="tel"
type="text"
fields={6}
onChange={setCode}
{...props}
className="mt-6 mb-2"
/>
</div>
<div className="block md:hidden">
<ReactCodeInput
name=""
inputMode="tel"
type="text"
fields={6}
onChange={setCode}
{...propsPhone}
className="mt-2 mb-6"
/>
</div>
{codeError && <Error text={t("signup:step2-code-error")} />}
<div className="flex max-w-max min-w-28 flex-col items-center justify-center md:p-2 max-h-24 mx-auto text-lg px-4 mt-4 mb-2">
<Button
text={t("signup:verify") ?? ""}
onButtonPressed={incrementStep}
size="lg"
/>
</div>
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
<div className="flex flex-row items-baseline gap-1 text-sm">
<span className="text-gray-400">
Not seeing an email?
</span>
<u className={`font-normal ${isResendingVerificationEmail ? 'text-gray-400' : 'text-primary-500 hover:opacity-90 duration-200'}`}>
<button disabled={isLoading} onClick={resendVerificationEmail}>
{isResendingVerificationEmail ? "Resending..." : "Resend"}
</button>
</u>
</div>
<p className="text-sm text-gray-500 pb-2">
{t("signup:step2-spam-alert")}
</p>
</div>
</div>
);
// Step 3 of the signup process (enter the rest of the impformation)
const step3 = (
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-36 md:mb-16">
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
{t("signup:step3-message")}
</p>
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
<InputField
label={t("common:first-name")}
onChangeHandler={setFirstName}
type="name"
value={firstName}
isRequired
errorText={
t("common:validate-required", {
name: t("common:first-name"),
}) as string
}
error={firstNameError}
autoComplete="given-name"
/>
</div>
<div className="mt-2 flex items-center justify-center w-full md:p-2 rounded-lg max-h-24">
<InputField
label={t("common:last-name")}
onChangeHandler={setLastName}
type="name"
value={lastName}
isRequired
errorText={
t("common:validate-required", {
name: t("common:last-name"),
}) as string
}
error={lastNameError}
autoComplete="family-name"
/>
</div>
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
<InputField
label={t("section-password:password")}
onChangeHandler={(password: string) => {
setPassword(password);
passwordCheck({
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck: false,
});
}}
type="password"
value={password}
isRequired
error={
passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase
}
autoComplete="new-password"
id="new-password"
/>
{passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber ? (
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
<div className={`text-gray-400 text-sm mb-1`}>
{t("section-password:validate-base")}
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLength ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
{t("section-password:validate-length")}
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
{t("section-password:validate-case")}
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
{t("section-password:validate-number")}
</div>
</div>
</div>
) : (
<div className="py-2"></div>
)}
</div>
<div className="flex flex-col items-center justify-center md:p-2 max-h-48 max-w-max mx-auto text-lg px-2 py-3">
<Button
text={t("signup:signup") ?? ""}
loading={isLoading}
onButtonPressed={signupErrorCheck}
size="lg"
/>
</div>
</div>
);
// Step 4 of the sign up process (download the emergency kit pdf)
const step4 = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
{t("signup:step4-message")}
</p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
<div>{t("signup:step4-description1")}</div>
<div className="mt-3">{t("signup:step4-description2")}</div>
</div>
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
{t("signup:step4-description3")}
</div>
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
<Button
text="Download PDF"
onButtonPressed={async () => {
await issueBackupKey({
email,
password,
personalName: firstName + " " + lastName,
setBackupKeyError,
setBackupKeyIssued,
});
const userWorkspaces = await getWorkspaces();
const userWorkspace = userWorkspaces[0]._id;
router.push("/home/" + userWorkspace);
}}
size="lg"
/>
{/* <div
className="text-l mt-4 text-lg text-gray-400 hover:text-gray-300 duration-200 bg-white/5 px-8 hover:bg-white/10 py-3 rounded-md cursor-pointer"
onClick={() => {
if (localStorage.getItem("projectData.id")) {
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
} else {
router.push("/noprojects")
}
}}
>
Later
</div> */}
</div>
</div>
);
return (
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
<Head>
@ -580,7 +91,11 @@ export default function SignUp() {
</div>
</Link>
<form onSubmit={(e) => e.preventDefault()}>
{step == 1 ? step1 : step == 2 ? step2 : step == 3 ? step3 : step4}
{step == 1 ? <EnterEmailStep email={email} setEmail={setEmail} incrementStep={incrementStep} />
: step == 2 ? <CodeInputStep email={email} incrementStep={incrementStep} setCode={setCode} codeError={codeError}/>
: step == 3 ? <UserInfoStep verificationToken={verificationToken} incrementStep={incrementStep} email={email} password={password} setPassword={setPassword} firstName={firstName} setFirstName={setFirstName} lastName={lastName} setLastName={setLastName}/>
: step == 4 ? <DownloadBackupPDF incrementStep={incrementStep} email={email} password={password} name={firstName + " " + lastName} />
: <TeamInviteStep/>}
</form>
</div>
</div>

View File

@ -349,7 +349,7 @@ export default function SignupInvite() {
setBackupKeyError,
setBackupKeyIssued
});
router.push('/dashboard/');
router.push('/noprojects/');
}}
size="lg"
/>

View File

@ -20,6 +20,22 @@ import addUserToWorkspace from '../api/workspace/addUserToWorkspace';
import getWorkspaceUsers from '../api/workspace/getWorkspaceUsers';
import uploadKeys from '../api/workspace/uploadKeys';
interface UserProps {
firstName: string;
lastName: string;
email: string;
_id: string;
publicKey: string;
}
interface MembershipProps {
user: UserProps
inviteEmail: string;
role: string;
status: string;
_id: string;
}
// #TODO: Update all the workspaceIds
const crypto = require('crypto');
const {
@ -30,10 +46,10 @@ const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');
export default function Users() {
let [isAddOpen, setIsAddOpen] = useState(false);
const [isAddOpen, setIsAddOpen] = useState(false);
// let [isDeleteOpen, setIsDeleteOpen] = useState(false);
// let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false);
let [email, setEmail] = useState('');
const [email, setEmail] = useState('');
const [personalEmail, setPersonalEmail] = useState('');
const [searchUsers, setSearchUsers] = useState('');
@ -59,7 +75,7 @@ export default function Users() {
// }
async function submitAddModal() {
let result = await addUserToWorkspace(email, router.query.id);
const result = await addUserToWorkspace(email, String(router.query.id));
if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
@ -77,7 +93,7 @@ export default function Users() {
privateKey: PRIVATE_KEY
});
uploadKeys(router.query.id, result.invitee._id, ciphertext, nonce);
uploadKeys(String(router.query.id), result.invitee._id, ciphertext, nonce);
}
setEmail('');
setIsAddOpen(false);
@ -88,41 +104,45 @@ export default function Users() {
setIsAddOpen(true);
}
const [userList, setUserList] = useState();
const [userList, setUserList] = useState([]);
const [orgUserList, setOrgUserList] = useState([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
const user = await getUser();
setPersonalEmail(user.email);
workspaceId = router.query.id;
let workspaceUsers = await getWorkspaceUsers({
workspaceId
});
const tempUserList = workspaceUsers.map((user) => ({
key: guidGenerator(),
firstName: user.user?.firstName,
lastName: user.user?.lastName,
email: user.user?.email == null ? user.inviteEmail : user.user?.email,
role: user?.role,
status: user?.status,
userId: user.user?._id,
membershipId: user._id,
publicKey: user.user?.publicKey
}));
setUserList(tempUserList);
const orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem('orgData.id')
});
setOrgUserList(orgUsers);
setEmail(
orgUsers
?.filter((user) => user.status == 'accepted')
.map((user) => user.user.email)
.filter(
(email) => !tempUserList?.map((user1) => user1.email).includes(email)
)[0]
);
useEffect(() => {
(async () => {
const user = await getUser();
setPersonalEmail(user.email);
// This part quiries the current users of a project
const workspaceUsers = await getWorkspaceUsers({
workspaceId: String(router.query.id)
});
const tempUserList = workspaceUsers.map((membership: MembershipProps) => ({
key: guidGenerator(),
firstName: membership.user?.firstName,
lastName: membership.user?.lastName,
email: membership.user?.email == null ? membership.inviteEmail : membership.user?.email,
role: membership?.role,
status: membership?.status,
userId: membership.user?._id,
membershipId: membership._id,
publicKey: membership.user?.publicKey
}));
setUserList(tempUserList);
// This is needed to know wha users from an org (if any), we are able to add to a certain project
const orgUsers = await getOrganizationUsers({
orgId: String(localStorage.getItem('orgData.id'))
});
setOrgUserList(orgUsers);
setEmail(
orgUsers
?.filter((membership: MembershipProps) => membership.status == 'accepted')
.map((membership: MembershipProps) => membership.user.email)
.filter(
(email: string) => !tempUserList?.map((user1: UserProps) => user1.email).includes(email)
)[0]
);
})();
}, []);
return userList ? (
@ -151,17 +171,17 @@ export default function Users() {
submitModal={submitAddModal}
email={email}
data={orgUserList
?.filter((user) => user.status == 'accepted')
.map((user) => user.user.email)
?.filter((membership: MembershipProps) => membership.status == 'accepted')
.map((membership: MembershipProps) => membership.user.email)
.filter(
(email) => !userList?.map((user1) => user1.email).includes(email)
(email) => !userList?.map((user1: UserProps) => user1.email).includes(email)
)}
workspaceId={workspaceId}
setEmail={setEmail}
/>
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
<div className="px-2 pb-1 w-full flex flex-row items-start max-w-5xl">
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center ml-4">
<div className="px-6 pb-1 w-full flex flex-row items-start min-w-6xl max-w-6xl">
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center">
<FontAwesomeIcon
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
icon={faMagnifyingGlass}
@ -170,12 +190,12 @@ export default function Users() {
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchUsers}
onChange={(e) => setSearchUsers(e.target.value)}
placeholder={t("section-members:search-members")}
placeholder={String(t("section-members:search-members"))}
/>
</div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start mr-4">
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
<Button
text={t("section-members:add-member")}
text={String(t("section-members:add-member"))}
onButtonPressed={openAddModal}
color="mineshaft"
size="md"
@ -183,7 +203,7 @@ export default function Users() {
/>
</div>
</div>
<div className="overflow-y-auto max-w-5xl mx-6">
<div className="block overflow-y-auto min-w-6xl max-w-6xl px-6">
<UserTable
userData={userList}
changeData={setUserList}
@ -194,7 +214,6 @@ export default function Users() {
// onClick={openDeleteModal}
// deleteUser={deleteMembership}
// setUserIdToBeDeleted={setUserIdToBeDeleted}
className="w-full mx-6"
/>
</div>
</div>

View File

@ -1,8 +1,11 @@
{
"title": "Activity Logs",
"subtitle": "Event history for this Infisical project.",
"event": {
"readSecrets": "Secrets Viewed",
"updateSecrets": "Secrets Updated",
"addSecrets": "Secrets Added",
"deleteSecrets": "Secrets Deleted"
}
},
"ip-address": "IP Address"
}

View File

@ -13,8 +13,8 @@
"project-id": "Project ID",
"save-changes": "Save Changes",
"saved": "Saved",
"drop-zone": "Drag and drop your .env file here.",
"drop-zone-keys": "Drag and drop your .env file here to add more keys.",
"drop-zone": "Drag and drop a .env or .yml file here.",
"drop-zone-keys": "Drag and drop a .env or .yml file here to add more keys.",
"role": "Role",
"role_admin": "admin",
"display-name": "Display Name",
@ -22,5 +22,13 @@
"expired-in": "Expires in",
"language": "Language",
"search": "Search...",
"note": "Note"
"note": "Note",
"view-more": "View More",
"end-of-history": "End of History",
"select-event": "Select an event",
"event": "Event",
"user": "User",
"source": "Source",
"time": "Time",
"timestamp": "Timestamp"
}

View File

@ -10,6 +10,7 @@
"shared-description": "Shared keys are visible to your whole team",
"make-shared": "Make Shared",
"make-personal": "Make Personal",
"add-secret": "Add a new secret",
"check-docs": {
"button": "Check Docs",
"title": "Good job!",
@ -25,6 +26,11 @@
"comments": "Comments & Notes",
"personal-explanation": "This secret is personal. It is not shared with any of your teammates.",
"generate-random-hex": "Generate Random Hex",
"digits": "digits"
"digits": "digits",
"delete-key-dialog": {
"title": "Delete Key",
"confirm-delete-message": "Are you sure you want to delete this secret? This cannot be undone."
}
}
}

View File

@ -17,5 +17,9 @@
"step4-description1": "If you get locked out of your account, your Emergency Kit is the only way to sign in.",
"step4-description2": "We recommend you download it and keep it somewhere safe.",
"step4-description3": "It contains your Secret Key which we cannot access or recover for you if you lose it.",
"step4-download": "Download PDF"
"step4-download": "Download PDF",
"step5-send-invites": "Send Invites",
"step5-invite-team": "Invite your team",
"step5-subtitle": "Infisical is meant to be used with your teammates. Invite them to test it out.",
"step5-skip": "Skip"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.8 MiB