Fix merge conflicts for index

This commit is contained in:
Tuan Dang
2022-12-15 15:31:14 -05:00
35 changed files with 1689 additions and 214 deletions
+3
View File
@@ -0,0 +1,3 @@
node_modules
built
healthcheck.js
@@ -0,0 +1,30 @@
version: '3'
services:
backend:
container_name: infisical-backend-test
restart: unless-stopped
depends_on:
- mongo
image: infisical/backend:test
command: npm run start
environment:
- NODE_ENV=production
- MONGO_URL=mongodb://test:example@mongo:27017/?authSource=admin
- MONGO_USERNAME=test
- MONGO_PASSWORD=example
networks:
- infisical-test
mongo:
container_name: infisical-mongo-test
image: mongo
restart: always
environment:
- MONGO_INITDB_ROOT_USERNAME=test
- MONGO_INITDB_ROOT_PASSWORD=example
networks:
- infisical-test
networks:
infisical-test:
+26
View File
@@ -0,0 +1,26 @@
# Name of the target container to check
container_name="$1"
# Timeout in seconds. Default: 60
timeout=$((${2:-60}));
if [ -z $container_name ]; then
echo "No container name specified";
exit 1;
fi
echo "Container: $container_name";
echo "Timeout: $timeout sec";
try=0;
is_healthy="false";
while [ $is_healthy != "true" ];
do
try=$(($try + 1));
printf "■";
is_healthy=$(docker inspect --format='{{json .State.Health}}' $container_name | jq '.Status == "healthy"');
sleep 1;
if [[ $try -eq $timeout ]]; then
echo " Container was not ready within timeout";
exit 1;
fi
done
+42 -46
View File
@@ -3,40 +3,38 @@ name: Push to Docker Hub
on: [workflow_dispatch] on: [workflow_dispatch]
jobs: jobs:
backend-image: backend-image:
name: Build backend image name: Build backend image
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- - name: ☁️ Checkout source
name: ☁️ Checkout source
uses: actions/checkout@v3 uses: actions/checkout@v3
- - name: 🔧 Set up QEMU
name: 🔧 Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- - name: 🔧 Set up Docker Buildx
name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- - name: 🐋 Login to Docker Hub
name: 🐋 Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
# - - name: 📦 Build backend and export to Docker
# name: 📦 Build backend and export to Docker uses: docker/build-push-action@v3
# uses: docker/build-push-action@v3 with:
# with: load: true
# load: true context: backend
# context: backend tags: infisical/backend:test
# tags: infisical/backend:test - name: ⏻ Spawn backend container and dependencies
# - run: |
# name: 🧪 Test backend image docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
# run: | - name: 🧪 Test backend image
# docker run --rm infisical/backend:test run: |
- ./.github/resources/healthcheck.sh infisical-backend-test
name: 🏗️ Build backend and push - name: ⏻ Shut down backend container and dependencies
run: |
docker compose -f .github/resources/docker-compose.be-test.yml down
- name: 🏗️ Build backend and push
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
push: true push: true
@@ -44,42 +42,40 @@ jobs:
tags: infisical/backend:latest tags: infisical/backend:latest
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
frontend-image: frontend-image:
name: Build frontend image name: Build frontend image
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- - name: ☁️ Checkout source
name: ☁️ Checkout source
uses: actions/checkout@v3 uses: actions/checkout@v3
- - name: 🔧 Set up QEMU
name: 🔧 Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- - name: 🔧 Set up Docker Buildx
name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- - name: 🐋 Login to Docker Hub
name: 🐋 Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
# - - name: 📦 Build frontend and export to Docker
# name: 📦 Build frontend and export to Docker uses: docker/build-push-action@v3
# uses: docker/build-push-action@v3 with:
# with: load: true
# load: true context: frontend
# context: frontend tags: infisical/frontend:test
# tags: infisical/frontend:test build-args: |
# build-args: | POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
# POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} - name: ⏻ Spawn frontend container
# - run: |
# name: 🧪 Test frontend image docker run -d --rm --name infisical-frontend-test infisical/frontend:test
# run: | - name: 🧪 Test frontend image
# docker run --rm infisical/frontend:test run: |
- ./.github/resources/healthcheck.sh infisical-frontend-test
name: 🏗️ Build frontend and push - name: ⏻ Shut down frontend container
run: |
docker stop infisical-frontend-test
- name: 🏗️ Build frontend and push
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
push: true push: true
+6 -3
View File
@@ -2,11 +2,14 @@ FROM node:16-bullseye-slim
WORKDIR /app WORKDIR /app
COPY package*.json . COPY package.json package-lock.json ./
RUN npm install RUN npm ci --only-production
COPY . . COPY . .
CMD ["npm", "run", "start"] HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node healthcheck.js
CMD ["npm", "run", "start"]
+24
View File
@@ -0,0 +1,24 @@
const http = require('http');
const PORT = process.env.PORT || 4000;
const options = {
host: 'localhost',
port: PORT,
timeout: 2000,
path: '/healthcheck'
};
const healthCheck = http.request(options, (res) => {
console.log(`HEALTHCHECK STATUS: ${res.statusCode}`);
if (res.statusCode == 200) {
process.exit(0);
} else {
process.exit(1);
}
});
healthCheck.on('error', function (err) {
console.error(`HEALTH CHECK ERROR: ${err}`);
process.exit(1);
});
healthCheck.end();
+154 -1
View File
@@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@godaddy/terminus": "^4.11.2",
"@sentry/node": "^7.14.0", "@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0", "@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
@@ -2028,6 +2029,14 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/@godaddy/terminus": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@godaddy/terminus/-/terminus-4.11.2.tgz",
"integrity": "sha512-e/kbOWpGKME42eltM/wXM3RxSUOrfureZxEd6Dt6NXyFoJ7E8lnmm7znXydJsL3B7ky4HRFZI+eHrep54NZbeQ==",
"dependencies": {
"stoppable": "^1.1.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.7", "version": "0.11.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
@@ -6731,7 +6740,129 @@
"treeverse", "treeverse",
"validate-npm-package-name", "validate-npm-package-name",
"which", "which",
"write-file-atomic" "write-file-atomic",
"@colors/colors",
"@gar/promisify",
"@npmcli/disparity-colors",
"@npmcli/git",
"@npmcli/installed-package-contents",
"@npmcli/metavuln-calculator",
"@npmcli/move-file",
"@npmcli/name-from-folder",
"@npmcli/node-gyp",
"@npmcli/promise-spawn",
"@npmcli/query",
"@tootallnate/once",
"agent-base",
"agentkeepalive",
"aggregate-error",
"ansi-regex",
"ansi-styles",
"aproba",
"are-we-there-yet",
"asap",
"balanced-match",
"bin-links",
"binary-extensions",
"brace-expansion",
"builtins",
"cidr-regex",
"clean-stack",
"clone",
"cmd-shim",
"color-convert",
"color-name",
"color-support",
"common-ancestor-path",
"concat-map",
"console-control-strings",
"cssesc",
"debug",
"debuglog",
"defaults",
"delegates",
"depd",
"dezalgo",
"diff",
"emoji-regex",
"encoding",
"env-paths",
"err-code",
"fs.realpath",
"function-bind",
"gauge",
"has",
"has-flag",
"has-unicode",
"http-cache-semantics",
"http-proxy-agent",
"https-proxy-agent",
"humanize-ms",
"iconv-lite",
"ignore-walk",
"imurmurhash",
"indent-string",
"infer-owner",
"inflight",
"inherits",
"ip",
"ip-regex",
"is-core-module",
"is-fullwidth-code-point",
"is-lambda",
"isexe",
"json-stringify-nice",
"jsonparse",
"just-diff",
"just-diff-apply",
"lru-cache",
"minipass-collect",
"minipass-fetch",
"minipass-flush",
"minipass-json-stream",
"minipass-sized",
"minizlib",
"mute-stream",
"negotiator",
"normalize-package-data",
"npm-bundled",
"npm-normalize-package-bin",
"npm-packlist",
"once",
"path-is-absolute",
"postcss-selector-parser",
"promise-all-reject-late",
"promise-call-limit",
"promise-inflight",
"promise-retry",
"promzard",
"read-cmd-shim",
"readable-stream",
"retry",
"safe-buffer",
"safer-buffer",
"set-blocking",
"signal-exit",
"smart-buffer",
"socks",
"socks-proxy-agent",
"spdx-correct",
"spdx-exceptions",
"spdx-expression-parse",
"spdx-license-ids",
"string_decoder",
"string-width",
"strip-ansi",
"supports-color",
"unique-filename",
"unique-slug",
"util-deprecate",
"validate-npm-package-license",
"walk-up-path",
"wcwidth",
"wide-align",
"wrappy",
"yallist"
], ],
"dev": true, "dev": true,
"dependencies": { "dependencies": {
@@ -10192,6 +10323,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/stoppable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
"engines": {
"node": ">=4",
"npm": ">=6"
}
},
"node_modules/strict-uri-encode": { "node_modules/strict-uri-encode": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
@@ -12607,6 +12747,14 @@
"strip-json-comments": "^3.1.1" "strip-json-comments": "^3.1.1"
} }
}, },
"@godaddy/terminus": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@godaddy/terminus/-/terminus-4.11.2.tgz",
"integrity": "sha512-e/kbOWpGKME42eltM/wXM3RxSUOrfureZxEd6Dt6NXyFoJ7E8lnmm7znXydJsL3B7ky4HRFZI+eHrep54NZbeQ==",
"requires": {
"stoppable": "^1.1.0"
}
},
"@humanwhocodes/config-array": { "@humanwhocodes/config-array": {
"version": "0.11.7", "version": "0.11.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
@@ -18631,6 +18779,11 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
}, },
"stoppable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="
},
"strict-uri-encode": { "strict-uri-encode": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+1
View File
@@ -1,5 +1,6 @@
{ {
"dependencies": { "dependencies": {
"@godaddy/terminus": "^4.11.2",
"@sentry/node": "^7.14.0", "@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0", "@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
+2 -2
View File
@@ -103,7 +103,7 @@ export const login2 = async (req: Request, res: Response) => {
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, { res.cookie('jid', tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/token', path: '/',
sameSite: 'strict', sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false secure: NODE_ENV === 'production' ? true : false
}); });
@@ -147,7 +147,7 @@ export const logout = async (req: Request, res: Response) => {
// clear httpOnly cookie // clear httpOnly cookie
res.cookie('jid', '', { res.cookie('jid', '', {
httpOnly: true, httpOnly: true,
path: '/token', path: '/',
sameSite: 'strict', sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false secure: NODE_ENV === 'production' ? true : false
}); });
+17 -16
View File
@@ -2,34 +2,35 @@ import rateLimit from 'express-rate-limit';
// 300 requests per 15 minutes // 300 requests per 15 minutes
const apiLimiter = rateLimit({ const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, windowMs: 15 * 60 * 1000,
max: 400, max: 400,
standardHeaders: true, standardHeaders: true,
legacyHeaders: false legacyHeaders: false,
skip: (request) => request.path === '/healthcheck'
}); });
// 5 requests per hour // 5 requests per hour
const signupLimiter = rateLimit({ const signupLimiter = rateLimit({
windowMs: 60 * 60 * 1000, windowMs: 60 * 60 * 1000,
max: 10, max: 10,
standardHeaders: true, standardHeaders: true,
legacyHeaders: false legacyHeaders: false
}); });
// 10 requests per hour // 10 requests per hour
const loginLimiter = rateLimit({ const loginLimiter = rateLimit({
windowMs: 60 * 60 * 1000, windowMs: 60 * 60 * 1000,
max: 20, max: 20,
standardHeaders: true, standardHeaders: true,
legacyHeaders: false legacyHeaders: false
}); });
// 5 requests per hour // 5 requests per hour
const passwordLimiter = rateLimit({ const passwordLimiter = rateLimit({
windowMs: 60 * 60 * 1000, windowMs: 60 * 60 * 1000,
max: 10, max: 10,
standardHeaders: true, standardHeaders: true,
legacyHeaders: false legacyHeaders: false
}); });
export { apiLimiter, signupLimiter, loginLimiter, passwordLimiter }; export { apiLimiter, signupLimiter, loginLimiter, passwordLimiter };
+62 -26
View File
@@ -1,3 +1,5 @@
/* eslint-disable no-console */
import http from 'http';
import express from 'express'; import express from 'express';
import helmet from 'helmet'; import helmet from 'helmet';
import cors from 'cors'; import cors from 'cors';
@@ -7,16 +9,17 @@ import dotenv from 'dotenv';
dotenv.config(); dotenv.config();
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import { PORT, SENTRY_DSN, NODE_ENV, MONGO_URL, SITE_URL, POSTHOG_PROJECT_API_KEY, POSTHOG_HOST, TELEMETRY_ENABLED } from './config'; import { PORT, SENTRY_DSN, NODE_ENV, MONGO_URL, SITE_URL } from './config';
import { apiLimiter } from './helpers/rateLimiter'; import { apiLimiter } from './helpers/rateLimiter';
import { createTerminus } from '@godaddy/terminus';
const app = express(); const app = express();
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
debug: NODE_ENV === 'production' ? false : true, debug: NODE_ENV === 'production' ? false : true,
environment: NODE_ENV environment: NODE_ENV
}); });
import { import {
@@ -40,31 +43,35 @@ import {
} from './routes'; } from './routes';
const connectWithRetry = () => { const connectWithRetry = () => {
mongoose.connect(MONGO_URL) mongoose
.then(() => console.log('Successfully connected to DB')) .connect(MONGO_URL)
.catch((e) => { .then(() => console.log('Successfully connected to DB'))
console.log('Failed to connect to DB ', e); .catch((e) => {
setTimeout(() => { console.log('Failed to connect to DB ', e);
console.log(e); setTimeout(() => {
}, 5000); console.log(e);
}); }, 5000);
} });
return mongoose.connection;
};
connectWithRetry(); const dbConnection = connectWithRetry();
app.enable('trust proxy'); app.enable('trust proxy');
app.use(cookieParser()); app.use(cookieParser());
app.use(cors({ app.use(
credentials: true, cors({
origin: SITE_URL credentials: true,
})); origin: SITE_URL
})
);
if (NODE_ENV === 'production') { if (NODE_ENV === 'production') {
// enable app-wide rate-limiting + helmet security // enable app-wide rate-limiting + helmet security
// in production // in production
app.disable('x-powered-by'); app.disable('x-powered-by');
app.use(apiLimiter); app.use(apiLimiter);
app.use(helmet()); app.use(helmet());
} }
app.use(express.json()); app.use(express.json());
@@ -88,6 +95,35 @@ app.use('/api/v1/stripe', stripeRouter);
app.use('/api/v1/integration', integrationRouter); app.use('/api/v1/integration', integrationRouter);
app.use('/api/v1/integration-auth', integrationAuthRouter); app.use('/api/v1/integration-auth', integrationAuthRouter);
app.listen(PORT, () => { const server = http.createServer(app);
console.log('Listening on PORT ' + PORT);
const onSignal = () => {
console.log('Server is starting clean-up');
return Promise.all([
() => {
dbConnection.close(() => {
console.info('Database connection closed');
});
}
]);
};
const healthCheck = () => {
// `state.isShuttingDown` (boolean) shows whether the server is shutting down or not
return Promise
.resolve
// optionally include a resolve value to be included as
// info in the health check response
();
};
createTerminus(server, {
healthChecks: {
'/healthcheck': healthCheck,
onSignal
}
});
server.listen(PORT, () => {
console.log('Listening on PORT ' + PORT);
}); });
+21 -16
View File
@@ -2,25 +2,30 @@ import { Schema, model } from 'mongoose';
import { EMAIL_TOKEN_LIFETIME } from '../config'; import { EMAIL_TOKEN_LIFETIME } from '../config';
export interface IToken { export interface IToken {
email: String; email: string;
token: String; token: string;
createdAt: Date; createdAt: Date;
} }
const tokenSchema = new Schema<IToken>({ const tokenSchema = new Schema<IToken>({
email: { email: {
type: String, type: String,
required: true required: true
}, },
token: { token: {
type: String, type: String,
required: true required: true
}, },
createdAt: { createdAt: {
type: Date, type: Date,
expires: parseInt(EMAIL_TOKEN_LIFETIME), default: Date.now
default: Date.now }
} });
tokenSchema.index({
createdAt: 1
}, {
expireAfterSeconds: parseInt(EMAIL_TOKEN_LIFETIME)
}); });
const Token = model<IToken>('Token', tokenSchema); const Token = model<IToken>('Token', tokenSchema);
+14 -10
View File
@@ -1,15 +1,19 @@
import { PostHog } from 'posthog-node'; import { PostHog } from 'posthog-node';
import { NODE_ENV, POSTHOG_HOST, POSTHOG_PROJECT_API_KEY, TELEMETRY_ENABLED } from '../config'; import {
NODE_ENV,
POSTHOG_HOST,
POSTHOG_PROJECT_API_KEY,
TELEMETRY_ENABLED
} from '../config';
console.log('TELEMETRY_ENABLED: ', TELEMETRY_ENABLED);
let postHogClient: any; let postHogClient: any;
if ( if (NODE_ENV === 'production' && TELEMETRY_ENABLED) {
NODE_ENV === 'production' // case: enable opt-out telemetry in production
&& TELEMETRY_ENABLED postHogClient = new PostHog(POSTHOG_PROJECT_API_KEY, {
) { host: POSTHOG_HOST
// case: enable opt-out telemetry in production });
postHogClient = new PostHog(POSTHOG_PROJECT_API_KEY, {
host: POSTHOG_HOST
});
} }
export default postHogClient; export default postHogClient;
+12 -1
View File
@@ -19,6 +19,7 @@ const (
FormatDotenv string = "dotenv" FormatDotenv string = "dotenv"
FormatJson string = "json" FormatJson string = "json"
FormatCSV string = "csv" FormatCSV string = "csv"
FormatYaml string = "yaml"
) )
// exportCmd represents the export command // exportCmd represents the export command
@@ -101,8 +102,10 @@ func formatEnvs(envs []models.SingleEnvironmentVariable, format string) (string,
return formatAsJson(envs), nil return formatAsJson(envs), nil
case FormatCSV: case FormatCSV:
return formatAsCSV(envs), nil return formatAsCSV(envs), nil
case FormatYaml:
return formatAsYaml(envs), nil
default: default:
return "", fmt.Errorf("invalid format flag: %s", format) return "", fmt.Errorf("invalid format type: %s. Available format types are [%s]", format, []string{FormatDotenv, FormatJson, FormatCSV, FormatYaml})
} }
} }
@@ -127,6 +130,14 @@ func formatAsDotEnv(envs []models.SingleEnvironmentVariable) string {
return dotenv return dotenv
} }
func formatAsYaml(envs []models.SingleEnvironmentVariable) string {
var dotenv string
for _, env := range envs {
dotenv += fmt.Sprintf("%s: %s\n", env.Key, env.Value)
}
return dotenv
}
// Format environment variables as a JSON file // Format environment variables as a JSON file
func formatAsJson(envs []models.SingleEnvironmentVariable) string { func formatAsJson(envs []models.SingleEnvironmentVariable) string {
// Dump as a json array // Dump as a json array
+1 -1
View File
@@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{
Short: "Infisical CLI is used to inject environment variables into any process", Short: "Infisical CLI is used to inject environment variables into any process",
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`, Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true}, CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
Version: "0.1.10", Version: "0.1.11",
} }
// Execute adds all child commands to the root command and sets flags appropriately. // Execute adds all child commands to the root command and sets flags appropriately.
+2 -2
View File
@@ -15,7 +15,7 @@ services:
- backend - backend
networks: networks:
- infisical - infisical
backend: backend:
container_name: infisical-backend container_name: infisical-backend
restart: unless-stopped restart: unless-stopped
@@ -28,7 +28,7 @@ services:
- NODE_ENV=production - NODE_ENV=production
networks: networks:
- infisical - infisical
frontend: frontend:
container_name: infisical-frontend container_name: infisical-frontend
restart: unless-stopped restart: unless-stopped
+3
View File
@@ -60,5 +60,8 @@ EXPOSE 3000
ENV PORT 3000 ENV PORT 3000
ENV NEXT_TELEMETRY_DISABLED 1 ENV NEXT_TELEMETRY_DISABLED 1
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node scripts/healthcheck.js
CMD ["/app/scripts/start.sh"] CMD ["/app/scripts/start.sh"]
-2
View File
@@ -1,5 +1,4 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useRouter } from 'next/router';
import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -26,7 +25,6 @@ const InputField = (
Pick<JSX.IntrinsicElements['input'], 'autoComplete' | 'id'> Pick<JSX.IntrinsicElements['input'], 'autoComplete' | 'id'>
) => { ) => {
const [passwordVisible, setPasswordVisible] = useState(false); const [passwordVisible, setPasswordVisible] = useState(false);
const router = useRouter();
if (props.static === true) { if (props.static === true) {
return ( return (
+5 -5
View File
@@ -41,7 +41,7 @@ interface LayoutProps {
export default function Layout({ children }: LayoutProps) { export default function Layout({ children }: LayoutProps) {
const router = useRouter(); const router = useRouter();
const [workspaceList, setWorkspaceList] = useState([]); const [workspaceList, setWorkspaceList] = useState([]);
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]); const [workspaceMapping, setWorkspaceMapping] = useState([{ '1': '2' }]);
const [workspaceSelected, setWorkspaceSelected] = useState('∞'); const [workspaceSelected, setWorkspaceSelected] = useState('∞');
const [newWorkspaceName, setNewWorkspaceName] = useState(''); const [newWorkspaceName, setNewWorkspaceName] = useState('');
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@@ -221,20 +221,20 @@ export default function Layout({ children }: LayoutProps) {
useEffect(() => { useEffect(() => {
try { try {
if ( if (
workspaceMapping[Number(workspaceSelected)] && workspaceMapping[workspaceSelected as any] &&
`${workspaceMapping[Number(workspaceSelected)]}` !== `${workspaceMapping[workspaceSelected as any]}` !==
router.asPath router.asPath
.split('/') .split('/')
[router.asPath.split('/').length - 1].split('?')[0] [router.asPath.split('/').length - 1].split('?')[0]
) { ) {
router.push( router.push(
'/dashboard/' + '/dashboard/' +
workspaceMapping[Number(workspaceSelected)] + workspaceMapping[workspaceSelected as any] +
'?Development' '?Development'
); );
localStorage.setItem( localStorage.setItem(
'projectData.id', 'projectData.id',
`${workspaceMapping[Number(workspaceSelected)]}` `${workspaceMapping[workspaceSelected as any]}`
); );
} }
} catch (error) { } catch (error) {
+18 -14
View File
@@ -63,13 +63,15 @@ const DropZone = ({
if (event.target === null || event.target.result === null) return; if (event.target === null || event.target.result === null) return;
// parse function's argument looks like to be ArrayBuffer // parse function's argument looks like to be ArrayBuffer
const keyPairs = parse(event.target.result as Buffer); const keyPairs = parse(event.target.result as Buffer);
const newData = Object.keys(keyPairs).map((key, index) => [ const newData = Object.keys(keyPairs).map((key, index) => {
guidGenerator(), return {
numCurrentRows + index, id: guidGenerator(),
key, pos: numCurrentRows + index,
keyPairs[key as keyof typeof keyPairs], key: key,
'shared' value: keyPairs[key as keyof typeof keyPairs],
]); type: 'shared'
};
});
setData(newData); setData(newData);
setButtonReady(true); setButtonReady(true);
}; };
@@ -97,13 +99,15 @@ const DropZone = ({
if (typeof result === 'string') { if (typeof result === 'string') {
const newData = result const newData = result
.split('\n') .split('\n')
.map((line: string, index: number) => [ .map((line: string, index: number) => {
guidGenerator(), return {
numCurrentRows + index, id: guidGenerator(),
line.split('=')[0], pos: numCurrentRows + index,
line.split('=').slice(1, line.split('=').length).join('='), key: line.split('=')[0],
'shared' value: line.split('=').slice(1, line.split('=').length).join('='),
]); type: 'shared'
};
});
setData(newData); setData(newData);
setButtonReady(true); setButtonReady(true);
} }
+619 -20
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -44,6 +44,7 @@
"react-redux": "^8.0.2", "react-redux": "^8.0.2",
"react-table": "^7.8.0", "react-table": "^7.8.0",
"set-cookie-parser": "^2.5.1", "set-cookie-parser": "^2.5.1",
"sharp": "^0.31.2",
"styled-components": "^5.3.5", "styled-components": "^5.3.5",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1", "tweetnacl-util": "^0.15.1",
@@ -0,0 +1,34 @@
interface Props {
email: string;
code: string;
}
/**
* This is the second part of the account recovery step (a user needs to verify their email).
* A user need to click on a button in a magic link page
* @param {object} obj
* @param {object} obj.email - email of a user that is trying to recover access to their account
* @param {object} obj.code - token that a use received via the magic link
* @returns
*/
const EmailVerifyOnPasswordReset = async ({ email, code }: Props) => {
const response = await fetch('/api/v1/password/email/password-reset-verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
code: code
})
});
if (response?.status === 200) {
return response;
}
throw new Error(
'Something went wrong during email verification on password reset.'
);
};
export default EmailVerifyOnPasswordReset;
@@ -0,0 +1,33 @@
interface Props {
email: string;
}
/**
* This is the first of the account recovery step (a user needs to verify their email).
* It will send an email containing a magic link to start the account recovery flow.
* @param {object} obj
* @param {object} obj.email - email of a user that is trying to recover access to their account
* @returns
*/
const SendEmailOnPasswordReset = async ({ email }: Props) => {
const response = await fetch('/api/v1/password/email/password-reset', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email
})
});
// need precise error handling about the status code
if (response?.status === 200) {
const data = await response.json();
return data;
}
throw new Error(
'Something went wrong while sending the email verification for password reset.'
);
};
export default SendEmailOnPasswordReset;
@@ -0,0 +1,26 @@
/**
* This is the route that get an encrypted private key (will be decrypted with a backup key)
* @param {object} obj
* @param {object} obj.verificationToken - this is the token that confirms that a user is the right one
* @returns
*/
const getBackupEncryptedPrivateKey = ({
verificationToken
}: {
verificationToken: string;
}) => {
return fetch('/api/v1/password/backup-private-key', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + verificationToken
}
}).then(async (res) => {
if (res?.status !== 200) {
console.log('Failed to get the backup key');
}
return (await res?.json())?.backupPrivateKey;
});
};
export default getBackupEncryptedPrivateKey;
@@ -0,0 +1,50 @@
interface Props {
verificationToken: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
salt: string;
verifier: string;
}
/**
* This is the route that resets the account password if all the previus steps were passed
* @param {object} obj
* @param {object} obj.verificationToken - this is the token that confirms that a user is the right one
* @param {object} obj.encryptedPrivateKey - the new encrypted private key (encrypted using the new password)
* @param {object} obj.iv
* @param {object} obj.tag
* @param {object} obj.salt
* @param {object} obj.verifier
* @returns
*/
const resetPasswordOnAccountRecovery = ({
verificationToken,
encryptedPrivateKey,
iv,
tag,
salt,
verifier
}: Props) => {
return fetch('/api/v1/password/password-reset', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + verificationToken
},
body: JSON.stringify({
encryptedPrivateKey: encryptedPrivateKey,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier
})
}).then(async (res) => {
if (res?.status !== 200) {
console.log('Failed to get the backup key');
}
return res;
});
};
export default resetPasswordOnAccountRecovery;
+22
View File
@@ -0,0 +1,22 @@
import React from 'react';
import Head from 'next/head';
export default function Activity() {
return (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
<Head>
<title>Request a New Invite</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-col items-center justify-center text-gray-200 h-screen w-screen">
<p className="text-6xl">Oops.</p>
<p className="mt-2 mb-1 text-xl">Your email was not verified. </p>
<p className="text-xl">Please try again.</p>
<p className="text-md mt-8 text-gray-600 max-w-sm text-center">
Note: If it still {"doesn't work"}, please reach out to us at
support@infisical.com
</p>
</div>
</div>
);
}
@@ -1,34 +1,37 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import Head from "next/head"; import Head from 'next/head';
import Image from "next/image"; import Image from 'next/image';
import Link from "next/link"; import Link from 'next/link';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faWarning } from "@fortawesome/free-solid-svg-icons"; import { faWarning } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from "~/components/basic/buttons/Button"; import Button from '~/components/basic/buttons/Button';
import Error from "~/components/basic/Error"; import Error from '~/components/basic/Error';
import InputField from "~/components/basic/InputField"; import InputField from '~/components/basic/InputField';
import attemptLogin from "~/utilities/attemptLogin"; import attemptLogin from '~/utilities/attemptLogin';
import getWorkspaces from "./api/workspace/getWorkspaces"; import getWorkspaces from './api/workspace/getWorkspaces';
export default function Login() { export default function Login() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState('');
const [password, setPassword] = useState(""); const [password, setPassword] = useState('');
const [errorLogin, setErrorLogin] = useState(false); const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const router = useRouter(); const router = useRouter();
useEffect(async () => { useEffect(() => {
let userWorkspace; const redirectToDashboard = async () => {
try { let userWorkspace;
const userWorkspaces = await getWorkspaces(); try {
userWorkspace = userWorkspaces[0]._id; const userWorkspaces = await getWorkspaces();
router.push("/dashboard/" + userWorkspace); userWorkspace = userWorkspaces[0]._id;
} catch (error) { router.push('/dashboard/' + userWorkspace);
console.log("Error - Not logged in yet"); } catch (error) {
} console.log('Error - Not logged in yet');
}
};
redirectToDashboard();
}, []); }, []);
/** /**
@@ -73,23 +76,9 @@ export default function Login() {
</div> </div>
</Link> </Link>
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl"> <div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
<p className="text-4xl flex justify-center font-semibold text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary"> <p className="text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-6">
Log In Log in to your account
</p> </p>
<div className="flex flex-row items-center justify-center">
<p className="text-md flex justify-center mt-2 text-gray-400">
Need an Infisical account?
</p>
</div>
<div className="flex flex-col items-center justify-center w-full md:pb-4 max-h-24 max-w-md mx-auto">
<Link href="/signup">
<button className="w-full pb-3 hover:opacity-90 duration-200">
<u className="font-normal text-md text-sky-500">
Create an account
</u>
</button>
</Link>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28"> <div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
<InputField <InputField
label="Email" label="Email"
@@ -98,10 +87,10 @@ export default function Login() {
value={email} value={email}
placeholder="" placeholder=""
isRequired isRequired
autocomplete="username" autoComplete="username"
/> />
</div> </div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28"> <div className="relative flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
<InputField <InputField
label="Password" label="Password"
onChangeHandler={setPassword} onChangeHandler={setPassword}
@@ -112,6 +101,9 @@ export default function Login() {
autoComplete="current-password" autoComplete="current-password"
id="current-password" id="current-password"
/> />
<div className="absolute top-2 right-3 text-primary-700 hover:text-primary duration-200 cursor-pointer text-sm">
<Link href="/verify-email">Forgot password?</Link>
</div>
</div> </div>
{errorLogin && <Error text="Your email and/or password are wrong." />} {errorLogin && <Error text="Your email and/or password are wrong." />}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm"> <div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
@@ -135,6 +127,16 @@ export default function Login() {
solving it right now. Please come back in a few minutes. solving it right now. Please come back in a few minutes.
</div> </div>
)} )}
<div className="flex flex-row items-center justify-center md:pb-4 mt-4">
<p className="text-sm flex justify-center text-gray-400 w-max">
Need an Infisical account?
</p>
<Link href="/signup">
<button className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5">
Sign up here.
</button>
</Link>
</div>
</div> </div>
); );
} }
+290
View File
@@ -0,0 +1,290 @@
import React, { useState } from 'react';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from '~/components/basic/buttons/Button';
import InputField from '~/components/basic/InputField';
import passwordCheck from '~/components/utilities/checks/PasswordCheck';
import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm';
import EmailVerifyOnPasswordReset from './api/auth/EmailVerifyOnPasswordReset';
import getBackupEncryptedPrivateKey from './api/auth/getBackupEncryptedPrivateKey';
import resetPasswordOnAccountRecovery from './api/auth/resetPasswordOnAccountRecovery';
const queryString = require('query-string');
const nacl = require('tweetnacl');
const jsrp = require('jsrp');
nacl.util = require('tweetnacl-util');
const client = new jsrp.client();
export default function PasswordReset() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const token = parsedUrl.token;
const email = parsedUrl.to?.replace(' ', '+').trim();
const [verificationToken, setVerificationToken] = useState('');
const [step, setStep] = useState(1);
const [backupKey, setBackupKey] = useState('');
const [privateKey, setPrivateKey] = useState('');
const [newPassword, setNewPassword] = useState('');
const [backupKeyError, setBackupKeyError] = useState(false);
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
// Unencrypt the private key with a backup key
const getEncryptedKeyHandler = async () => {
try {
const result = await getBackupEncryptedPrivateKey({ verificationToken });
setPrivateKey(
Aes256Gcm.decrypt({
ciphertext: result.encryptedPrivateKey,
iv: result.iv,
tag: result.tag,
secret: backupKey
})
);
setStep(3);
} catch {
setBackupKeyError(true);
}
};
// If everything is correct, reset the password
const resetPasswordHandler = async () => {
let errorCheck = false;
errorCheck = passwordCheck({
password: newPassword,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck: errorCheck
});
if (!errorCheck) {
// Generate a random pair of a public and a private key
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
text: privateKey,
secret: newPassword
.slice(0, 32)
.padStart(
32 +
(newPassword.slice(0, 32).length - new Blob([newPassword]).size),
'0'
)
}) as { ciphertext: string; iv: string; tag: string };
client.init(
{
username: email,
password: newPassword
},
async () => {
client.createVerifier(
async (err: any, result: { salt: string; verifier: string }) => {
const response = await resetPasswordOnAccountRecovery({
verificationToken,
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier
});
// if everything works, go the main dashboard page.
if (response?.status === 200) {
router.push('/login');
}
}
);
}
);
}
};
// Click a button to confirm email
const stepConfirmEmail = (
<div className="bg-bunker flex flex-col items-center w-full py-6 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold mb-8 flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
Confirm your email
</p>
<Image
src="/images/envelope.svg"
height={262}
width={410}
alt="verify email"
></Image>
<div className="flex max-w-max flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-4 mb-2">
<Button
text="Confirm Email"
onButtonPressed={async () => {
const response = await EmailVerifyOnPasswordReset({
email,
code: token
});
if (response.status == 200) {
setVerificationToken((await response.json()).token);
setStep(2);
} else {
console.log('ERROR', response);
router.push('/email-not-verified');
}
}}
size="lg"
/>
</div>
</div>
);
// Input backup key
const stepInputBackupKey = (
<div className="bg-bunker flex flex-col items-center w-full pt-6 pb-3 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl">
<p className="text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-4">
Enter your backup key
</p>
<div className="flex flex-row items-center justify-center md:pb-4 mt-4 md:mx-2">
<p className="text-sm flex justify-center text-gray-400 w-max max-w-md">
You can find it in your emrgency kit. You had to download the enrgency
kit during signup.
</p>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
<InputField
label="Backup Key"
onChangeHandler={setBackupKey}
type="password"
value={backupKey}
placeholder=""
isRequired
error={backupKeyError}
errorText="Something is wrong with the backup key"
/>
</div>
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button
text="Submit Backup Key"
onButtonPressed={() => getEncryptedKeyHandler()}
size="lg"
/>
</div>
</div>
</div>
);
// Enter new password
const stepEnterNewPassword = (
<div className="bg-bunker flex flex-col items-center w-full pt-6 pb-3 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl">
<p className="text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100">
Enter new password
</p>
<div className="flex flex-row items-center justify-center md:pb-4 mt-1 md:mx-2">
<p className="text-sm flex justify-center text-gray-400 w-max max-w-md">
Make sure you save it somewhere save.
</p>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
<InputField
label="New Password"
onChangeHandler={(password) => {
setNewPassword(password);
passwordCheck({
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck: false
});
}}
type="password"
value={newPassword}
isRequired
error={
passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber
}
autoComplete="new-password"
id="new-password"
/>
</div>
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
<div className="w-full mt-3 bg-white/5 px-2 mx-2 flex flex-col items-start py-2 rounded-md max-w-md mb-2">
<div className={`text-gray-400 text-sm mb-1`}>
Password should contain at least:
</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`}
>
14 characters
</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`}
>
1 lowercase character
</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`}
>
1 number
</div>
</div>
</div>
) : (
<div className="py-2"></div>
)}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button
text="Submit New Password"
onButtonPressed={() => resetPasswordHandler()}
size="lg"
/>
</div>
</div>
</div>
);
return (
<div className="bg-bunker-800 h-screen w-full flex flex-col items-center justify-center">
{step === 1 && stepConfirmEmail}
{step === 2 && stepInputBackupKey}
{step === 3 && stepEnterNewPassword}
</div>
);
}
+5 -5
View File
@@ -33,7 +33,7 @@ export default function SignupInvite() {
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const router = useRouter(); const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]); const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const [email, setEmail] = useState(parsedUrl.to); const [email, setEmail] = useState(parsedUrl.to?.replace(' ', '+').trim());
const token = parsedUrl.token; const token = parsedUrl.token;
const [errorLogin, setErrorLogin] = useState(false); const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@@ -58,13 +58,13 @@ export default function SignupInvite() {
} else { } else {
setLastNameError(false); setLastNameError(false);
} }
errorCheck = passwordCheck( errorCheck = passwordCheck({
password, password,
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
errorCheck errorCheck
); });
if (!errorCheck) { if (!errorCheck) {
// Generate a random pair of a public and a private key // Generate a random pair of a public and a private key
@@ -150,7 +150,7 @@ export default function SignupInvite() {
width={410} width={410}
alt="verify email" alt="verify email"
></Image> ></Image>
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto text-lg py-1 text-center md:text-left"> <div className="flex max-w-max flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-4 mb-2">
<Button <Button
text="Confirm Email" text="Confirm Email"
onButtonPressed={async () => { onButtonPressed={async () => {
@@ -329,7 +329,7 @@ export default function SignupInvite() {
It contains your Secret Key which we cannot access or recover for you if It contains your Secret Key which we cannot access or recover for you if
you lose it. you lose it.
</div> </div>
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-6 md:mt-8 py-1 text-lg text-center md:text-left"> <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 <Button
text="Download PDF" text="Download PDF"
onButtonPressed={async () => { onButtonPressed={async () => {
+94
View File
@@ -0,0 +1,94 @@
import React, { useState } from 'react';
import Head from 'next/head';
import Image from 'next/image';
import Link from 'next/link';
import Button from '~/components/basic/buttons/Button';
import InputField from '~/components/basic/InputField';
import SendEmailOnPasswordReset from './api/auth/SendEmailOnPasswordReset';
export default function VerifyEmail() {
const [email, setEmail] = useState('');
const [step, setStep] = useState(1);
/**
* This function sends the verification email and forwards a user to the next step.
*/
const sendVerificationEmail = async () => {
if (email) {
await SendEmailOnPasswordReset({ email });
setStep(2);
}
};
return (
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
<Head>
<title>Login</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta property="og:title" content="Log In to Infisical" />
<meta
name="og:description"
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
/>
</Head>
<Link href="/">
<div className="flex justify-center mb-8 mt-20 cursor-pointer">
<Image
src="/images/biglogo.png"
height={90}
width={120}
alt="long logo"
/>
</div>
</Link>
{step == 1 && (
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
<p className="text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-6">
Forgot your password?
</p>
<div className="flex flex-row items-center justify-center md:pb-4 mt-4 md:mx-2">
<p className="text-sm flex justify-center text-gray-400 w-max">
You will need your emergency kit. Enter your email to start
account recovery.
</p>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
<InputField
label="Email"
onChangeHandler={setEmail}
type="email"
value={email}
placeholder=""
isRequired
autoComplete="username"
/>
</div>
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button
text="Continue"
onButtonPressed={sendVerificationEmail}
size="lg"
/>
</div>
</div>
</div>
)}
{step == 2 && (
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
<p className="text-xl md:text-2xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-6">
Look for an email in your inbox.
</p>
<div className="flex flex-row items-center justify-center md:pb-4 mt-4 md:mx-2">
<p className="text-sm flex justify-center text-gray-400 w-max text-center">
An email with instructions has been sent to {email}.
</p>
</div>
</div>
)}
</div>
);
}
+23
View File
@@ -0,0 +1,23 @@
const http = require('http');
const options = {
host: 'localhost',
port: 3000,
timeout: 2000,
path: '/'
};
const healthCheck = http.request(options, (res) => {
console.log(`HEALTHCHECK STATUS: ${res.statusCode}`);
if (res.statusCode == 200) {
process.exit(0);
} else {
process.exit(1);
}
});
healthCheck.on('error', function (err) {
console.error(`HEALTH CHECK ERROR: ${err}`);
process.exit(1);
});
healthCheck.end();
+1 -1
View File
@@ -7,7 +7,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.2 version: 0.1.3
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
@@ -22,11 +22,11 @@ spec:
- containerPort: 4000 - containerPort: 4000
env: env:
{{- range $key, $value := .Values.backendEnvironmentVariables }} {{- range $key, $value := .Values.backendEnvironmentVariables }}
{{- if eq $value "MUST_REPLACE" }} {{- if $value | quote | eq "MUST_REPLACE" }}
{{ fail "Environment variables are not set. Please set all environment variables to continue." }} {{ fail "Environment variables are not set. Please set all environment variables to continue." }}
{{ end }} {{ end }}
- name: {{ $key }} - name: {{ $key }}
value: {{ $value }} value: {{ quote $value }}
{{- end }} {{- end }}
--- ---
@@ -20,6 +20,9 @@ spec:
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
env: env:
{{- range $key, $value := .Values.frontendEnvironmentVariables }} {{- range $key, $value := .Values.frontendEnvironmentVariables }}
{{- if $value | quote | eq "MUST_REPLACE" }}
{{ fail "Environment variables are not set. Please set all environment variables to continue." }}
{{ end }}
- name: {{ $key }} - name: {{ $key }}
value: {{ quote $value }} value: {{ quote $value }}
{{- end }} {{- end }}