mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 03:25:48 +00:00
Fix merge conflicts for index
This commit is contained in:
@@ -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:
|
||||||
Executable
+26
@@ -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
|
||||||
@@ -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
@@ -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"]
|
||||||
|
|||||||
@@ -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();
|
||||||
Generated
+154
-1
@@ -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,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",
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+619
-20
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
@@ -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 }}
|
||||||
|
|||||||
Reference in New Issue
Block a user