mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 03:29:35 +00:00
@@ -1,4 +1,9 @@
|
||||
module.exports = {
|
||||
overrides: [
|
||||
{
|
||||
files: ["next.config.js"]
|
||||
}
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
@@ -87,6 +92,7 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
},
|
||||
ignorePatterns: ['next.config.js'],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
|
||||
+29
-1
@@ -1,9 +1,11 @@
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
**/
|
||||
const { i18n } = require("./next-i18next.config.js");
|
||||
const path = require('path');
|
||||
|
||||
const ContentSecurityPolicy = `
|
||||
default-src 'self';
|
||||
@@ -65,7 +67,33 @@ module.exports = {
|
||||
},
|
||||
];
|
||||
},
|
||||
webpack: (config, { isServer, webpack }) => {
|
||||
webpack: (config, { isServer, webpack }) => { // config
|
||||
config.module.rules.push({
|
||||
test: /\.wasm$/,
|
||||
loader: "base64-loader",
|
||||
type: "javascript/auto",
|
||||
});
|
||||
|
||||
config.module.noParse = /\.wasm$/;
|
||||
|
||||
config.module.rules.forEach((rule) => {
|
||||
(rule.oneOf || []).forEach((oneOf) => {
|
||||
if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) {
|
||||
oneOf.exclude.push(/\.wasm$/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!isServer) {
|
||||
config.resolve.fallback.fs = false;
|
||||
}
|
||||
|
||||
// Perform customizations to webpack config
|
||||
config.plugins.push(
|
||||
new webpack.IgnorePlugin({ resourceRegExp: /\/__tests__\// })
|
||||
);
|
||||
|
||||
// Important: return the modified config
|
||||
return config;
|
||||
},
|
||||
i18n,
|
||||
|
||||
Generated
+33
-735
@@ -33,9 +33,12 @@
|
||||
"@stripe/react-stripe-js": "^1.10.0",
|
||||
"@stripe/stripe-js": "^1.46.0",
|
||||
"@tanstack/react-query": "^4.23.0",
|
||||
"@types/argon2-browser": "^1.18.1",
|
||||
"add": "^2.0.6",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"axios": "^0.27.2",
|
||||
"axios-auth-refresh": "^3.3.3",
|
||||
"base64-loader": "^1.0.0",
|
||||
"classnames": "^2.3.1",
|
||||
"cookies": "^0.8.0",
|
||||
"cva": "npm:class-variance-authority@^0.4.0",
|
||||
@@ -2339,54 +2342,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz",
|
||||
"integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg=="
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
|
||||
"integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
|
||||
@@ -2403,294 +2358,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
|
||||
"integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
|
||||
"integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
|
||||
"integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
|
||||
"integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
|
||||
"integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
|
||||
"integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
|
||||
"integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
|
||||
"integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
|
||||
@@ -3293,36 +2960,6 @@
|
||||
"integrity": "sha512-NXGXGFGiOKEnvBIHq9cdFTKbHO2/4B3Zd9K27M7j1DioIQVar7oVRqZMYs0h3XMVEZLwjjkdAtqRPCzzd3RtXg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@next/swc-android-arm-eabi": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.4.tgz",
|
||||
"integrity": "sha512-cM42Cw6V4Bz/2+j/xIzO8nK/Q3Ly+VSlZJTa1vHzsocJRYz8KT6MrreXaci2++SIZCF1rVRCDgAg5PpqRibdIA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-android-arm64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.4.tgz",
|
||||
"integrity": "sha512-5jf0dTBjL+rabWjGj3eghpLUxCukRhBcEJgwLedewEA/LJk2HyqCvGIwj5rH+iwmq1llCWbOky2dO3pVljrapg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.4.tgz",
|
||||
@@ -3338,156 +2975,6 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.4.tgz",
|
||||
"integrity": "sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-freebsd-x64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.4.tgz",
|
||||
"integrity": "sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm-gnueabihf": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.4.tgz",
|
||||
"integrity": "sha512-3zqD3pO+z5CZyxtKDTnOJ2XgFFRUBciOox6EWkoZvJfc9zcidNAQxuwonUeNts6Xbm8Wtm5YGIRC0x+12YH7kw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.4.tgz",
|
||||
"integrity": "sha512-kiX0vgJGMZVv+oo1QuObaYulXNvdH/IINmvdZnVzMO/jic/B8EEIGlZ8Bgvw8LCjH3zNVPO3mGrdMvnEEPEhKA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.4.tgz",
|
||||
"integrity": "sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.4.tgz",
|
||||
"integrity": "sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.4.tgz",
|
||||
"integrity": "sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.4.tgz",
|
||||
"integrity": "sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-ia32-msvc": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.4.tgz",
|
||||
"integrity": "sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.4.tgz",
|
||||
"integrity": "sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -6956,6 +6443,11 @@
|
||||
"@testing-library/dom": ">=7.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/argon2-browser": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.18.1.tgz",
|
||||
"integrity": "sha512-PZffP/CqH9m2kovDSRQMfMMxUC3V98I7i7/caa0RB0/nvsXzYbL9bKyqZpNMFmLFGZslROlG1R60ONt7abrwlA=="
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
|
||||
@@ -8364,6 +7856,11 @@
|
||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/argon2-browser": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz",
|
||||
"integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw=="
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
@@ -8958,6 +8455,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/base64-loader": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64-loader/-/base64-loader-1.0.0.tgz",
|
||||
"integrity": "sha512-p32+F8dg+ANGx7s8QsZS74ZPHfIycmC2yZcoerzFgbersIYWitPbbF39G6SBx3gyvzyLH5nt1ooocxr0IHuWKA=="
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||
@@ -24300,27 +23802,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz",
|
||||
"integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg=="
|
||||
},
|
||||
"@esbuild/android-arm": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
|
||||
"integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/android-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/android-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/darwin-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
|
||||
@@ -24328,132 +23809,6 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/darwin-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/freebsd-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/freebsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-arm": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
|
||||
"integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-ia32": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
|
||||
"integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-loong64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
|
||||
"integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-mips64el": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
|
||||
"integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-ppc64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
|
||||
"integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-riscv64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
|
||||
"integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-s390x": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
|
||||
"integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/netbsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/openbsd-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/sunos-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-arm64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
|
||||
"integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-ia32": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
|
||||
"integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/win32-x64": {
|
||||
"version": "0.16.17",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
|
||||
"integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
|
||||
@@ -24904,84 +24259,12 @@
|
||||
"integrity": "sha512-NXGXGFGiOKEnvBIHq9cdFTKbHO2/4B3Zd9K27M7j1DioIQVar7oVRqZMYs0h3XMVEZLwjjkdAtqRPCzzd3RtXg==",
|
||||
"dev": true
|
||||
},
|
||||
"@next/swc-android-arm-eabi": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.4.tgz",
|
||||
"integrity": "sha512-cM42Cw6V4Bz/2+j/xIzO8nK/Q3Ly+VSlZJTa1vHzsocJRYz8KT6MrreXaci2++SIZCF1rVRCDgAg5PpqRibdIA==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-android-arm64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.4.tgz",
|
||||
"integrity": "sha512-5jf0dTBjL+rabWjGj3eghpLUxCukRhBcEJgwLedewEA/LJk2HyqCvGIwj5rH+iwmq1llCWbOky2dO3pVljrapg==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-darwin-arm64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.4.tgz",
|
||||
"integrity": "sha512-DqsSTd3FRjQUR6ao0E1e2OlOcrF5br+uegcEGPVonKYJpcr0MJrtYmPxd4v5T6UCJZ+XzydF7eQo5wdGvSZAyA==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-darwin-x64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.4.tgz",
|
||||
"integrity": "sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-freebsd-x64": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.4.tgz",
|
||||
"integrity": "sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-linux-arm-gnueabihf": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.4.tgz",
|
||||
"integrity": "sha512-3zqD3pO+z5CZyxtKDTnOJ2XgFFRUBciOox6EWkoZvJfc9zcidNAQxuwonUeNts6Xbm8Wtm5YGIRC0x+12YH7kw==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-linux-arm64-gnu": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.4.tgz",
|
||||
"integrity": "sha512-kiX0vgJGMZVv+oo1QuObaYulXNvdH/IINmvdZnVzMO/jic/B8EEIGlZ8Bgvw8LCjH3zNVPO3mGrdMvnEEPEhKA==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-linux-arm64-musl": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.4.tgz",
|
||||
"integrity": "sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-linux-x64-gnu": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.4.tgz",
|
||||
"integrity": "sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-linux-x64-musl": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.4.tgz",
|
||||
"integrity": "sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-win32-arm64-msvc": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.4.tgz",
|
||||
"integrity": "sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-win32-ia32-msvc": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.4.tgz",
|
||||
"integrity": "sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@next/swc-win32-x64-msvc": {
|
||||
"version": "12.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.4.tgz",
|
||||
"integrity": "sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==",
|
||||
"optional": true
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -27483,6 +26766,11 @@
|
||||
"@babel/runtime": "^7.12.5"
|
||||
}
|
||||
},
|
||||
"@types/argon2-browser": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.18.1.tgz",
|
||||
"integrity": "sha512-PZffP/CqH9m2kovDSRQMfMMxUC3V98I7i7/caa0RB0/nvsXzYbL9bKyqZpNMFmLFGZslROlG1R60ONt7abrwlA=="
|
||||
},
|
||||
"@types/aria-query": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
|
||||
@@ -28614,6 +27902,11 @@
|
||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||
"dev": true
|
||||
},
|
||||
"argon2-browser": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz",
|
||||
"integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw=="
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
@@ -29049,6 +28342,11 @@
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"base64-loader": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64-loader/-/base64-loader-1.0.0.tgz",
|
||||
"integrity": "sha512-p32+F8dg+ANGx7s8QsZS74ZPHfIycmC2yZcoerzFgbersIYWitPbbF39G6SBx3gyvzyLH5nt1ooocxr0IHuWKA=="
|
||||
},
|
||||
"before-after-hook": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||
|
||||
@@ -39,10 +39,13 @@
|
||||
"@reduxjs/toolkit": "^1.8.3",
|
||||
"@stripe/react-stripe-js": "^1.10.0",
|
||||
"@stripe/stripe-js": "^1.46.0",
|
||||
"@types/argon2-browser": "^1.18.1",
|
||||
"@tanstack/react-query": "^4.23.0",
|
||||
"add": "^2.0.6",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"axios": "^0.27.2",
|
||||
"axios-auth-refresh": "^3.3.3",
|
||||
"base64-loader": "^1.0.0",
|
||||
"classnames": "^2.3.1",
|
||||
"cookies": "^0.8.0",
|
||||
"cva": "npm:class-variance-authority@^0.4.0",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"title": "Sign Up",
|
||||
"og-title": "Replace .env files with 1 line of code. Sign Up for Infisical in 3 minutes.",
|
||||
"og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage API-keys and environemntal variables. Works with Node.js, Next.js, Gatsby, Nest.js...",
|
||||
"signup": "Sign Up",
|
||||
"already-have-account": "Have an account? Log in",
|
||||
"forgot-password": "Forgot your password?",
|
||||
"verify": "Verify",
|
||||
"step1-start": "Let's get started",
|
||||
"step1-privacy": "By creating an account, you agree to our Terms and have read and acknowledged the Privacy Policy.",
|
||||
"step1-submit": "Get Started",
|
||||
"step2-message": "We've sent a verification code to",
|
||||
"step2-code-error": "Oops. Your code is wrong. Tries left:",
|
||||
"step2-resend-alert": "Don't see the code?",
|
||||
"step2-resend-submit": "Resend",
|
||||
"step2-resend-progress": "Resending...",
|
||||
"step2-spam-alert": "Make sure to check your spam inbox.",
|
||||
"step3-message": "Almost there!",
|
||||
"step4-message": "Save your Emergency Kit",
|
||||
"step4-description1": "If you get locked out of your account, your Emergency Kit is the only way to sign in.",
|
||||
"step4-description2": "We recommend you download it and keep it somewhere safe.",
|
||||
"step4-description3": "It contains your Secret Key which we cannot access or recover for you if you lose it.",
|
||||
"step4-download": "Download PDF",
|
||||
"step5-send-invites": "Send Invites",
|
||||
"step5-invite-team": "Invite your team",
|
||||
"step5-subtitle": "Infisical is meant to be used with your teammates. Invite them to test it out.",
|
||||
"step5-skip": "Skip"
|
||||
}
|
||||
@@ -9,9 +9,9 @@
|
||||
"step1-start": "Let's get started",
|
||||
"step1-privacy": "By creating an account, you agree to our Terms and have read and acknowledged the Privacy Policy.",
|
||||
"step1-submit": "Get Started",
|
||||
"step2-message": "We've sent a verification email to",
|
||||
"step2-message": "We've sent a verification code to",
|
||||
"step2-code-error": "Oops. Your code is wrong. Please try again.",
|
||||
"step2-resend-alert": "Don't see the email?",
|
||||
"step2-resend-alert": "Don't see the code?",
|
||||
"step2-resend-submit": "Resend",
|
||||
"step2-resend-progress": "Resending...",
|
||||
"step2-spam-alert": "Make sure to check your spam inbox.",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"title": "S'inscrire",
|
||||
"og-title": "Remplacez les fichiers .env par 1 ligne de code. Inscrivez-vous à Infisical en 3 minutes.",
|
||||
"og-description": "Infisical, une plate-forme simple et chiffré de bout en bout qui permet aux équipes de synchroniser et de gérer des clefs API et des variables d'environnement. Fonctionne avec Node.js, Next.js, Gatsby, Nest.js ...",
|
||||
"signup": "S'inscrire",
|
||||
"already-have-account": "Déjà inscris? Se connecter",
|
||||
"forgot-password": "Mot de passe oublié?",
|
||||
"verify": "Vérifier",
|
||||
"step1-start": "Bon, on commence!",
|
||||
"step1-privacy": "En créant votre compte, vous acceptez nos conditions et avez lu et reconnu notre politique de confidentialité.",
|
||||
"step1-submit": "C'est parti",
|
||||
"step2-message": "Nous avons envoyé un email de vérification à",
|
||||
"step2-code-error": "Oops. Votre code est faux. Essais restants:",
|
||||
"step2-resend-alert": "Vous ne voyez pas le code?",
|
||||
"step2-resend-submit": "Renvoyer",
|
||||
"step2-resend-progress": "Envoie en cours...",
|
||||
"step2-spam-alert": "Assurez-vous de vérifier vos spams.",
|
||||
"step3-message": "Nous y sommes presque!",
|
||||
"step4-message": "Enregistrez votre kit d'urgence",
|
||||
"step4-description1": "Si vous n'arrivez plus à vous connecter à votre compte, votre kit d'urgence est le seul moyen d'y arriver.",
|
||||
"step4-description2": "Nous vous recommandons de le télécharger et de le garder en sécurité.",
|
||||
"step4-description3": "Il contient votre clef secrète que nous ne pouvons pas récupérer pour vous si vous la perdez.",
|
||||
"step4-download": "Téléchargez le PDF",
|
||||
"step5-send-invites": "Envoyer les invitations",
|
||||
"step5-invite-team": "Invitez votre équipe",
|
||||
"step5-subtitle": "Infisical a pour but d'être utilisé avec vos coéquipiers. Invitez-les à le tester.",
|
||||
"step5-skip": "Passer"
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
"step1-submit": "C'est parti",
|
||||
"step2-message": "Nous avons envoyé un email de vérification à",
|
||||
"step2-code-error": "Oops. Votre code est faux. Veuillez réessayer.",
|
||||
"step2-resend-alert": "Vous ne voyez pas l'email?",
|
||||
"step2-resend-alert": "Vous ne voyez pas le code?",
|
||||
"step2-resend-submit": "Renvoyer",
|
||||
"step2-resend-progress": "Envoie en cours...",
|
||||
"step2-spam-alert": "Assurez-vous de vérifier vos spams.",
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"title": "회원가입",
|
||||
"og-title": "한줄의 코드르 .env파일을 교체하세요. 3분이면 가입할 수 있어요.",
|
||||
"og-description": "Infisical은 팀원과 .env파일을 공유하고 연동할 수 있는 심플한 end-to-end 암호화 플렛폼입니다. Node.js, Next.js, Gatsby, Nest.js 와 같은 다양한 플렛폼에서 작동해요.",
|
||||
"signup": "회원가입",
|
||||
"already-have-account": "이미 계정이 있나요? 로그인하기",
|
||||
"forgot-password": "비밀번호를 잊으셨나요?",
|
||||
"verify": "인증",
|
||||
"step1-start": "시작하기",
|
||||
"step1-privacy": "회원가입시 약관과 개인 정보 보호 정책을 읽고 동의한 것으로 간주합니다.",
|
||||
"step1-submit": "시작하기",
|
||||
"step2-message": "<email>{{email}}</email><wrapper>로 인증 메일을 전송하였습니다</wrapper><email>{{email}}</email>",
|
||||
"step2-code-error": "코드가 잘못된 것 같아요. 남은 시도:",
|
||||
"step2-spam-alert": "스팸함에 메일이 있지는 않은지 확인하세요",
|
||||
"step3-message": "거의다 끝났어요!",
|
||||
"step4-message": "긴급복구 키트 저장하기",
|
||||
"step4-description1": "계정이 잠겼을 경우 비상 키트를 사용하여 로그인할 수 있어요.",
|
||||
"step4-description2": "다운로드 후 안전한 곳에 보관하는 것을 추천합니다.",
|
||||
"step4-description3": "분실시 접근하거나 복구할 수 없는 시크릿 키가 포함되어 있어요.",
|
||||
"step4-download": "PDF 다운로드"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"title": "Inscrever-se",
|
||||
"og-title": "Substitua os arquivos .env por 1 linha de código. Cadastre-se no Infisical em 3 minutos.",
|
||||
"og-description": "Infisical é uma plataforma criptografada de ponta a ponta simples que permite que as equipes sincronizem e gerenciem chaves de API e variáveis ambientais. Funciona com Node.js, Next.js, Gatsby, Nest.js...",
|
||||
"signup": "Inscrever-se",
|
||||
"already-have-account": "Possui uma conta? Conecte-se",
|
||||
"forgot-password": "Esqueceu sua senha?",
|
||||
"verify": "Verificar",
|
||||
"step1-start": "Vamos começar",
|
||||
"step1-privacy": "Ao criar uma conta, você concorda com nossos Termos e leu e reconheceu a Política de Privacidade.",
|
||||
"step1-submit": "Iniciar",
|
||||
"step2-message": "<wrapper>Enviamos um e-mail de verificação para</wrapper><email>{{email}}</email>",
|
||||
"step2-code-error": "Ops. Seu código está errado. Tentativas restantes:",
|
||||
"step2-spam-alert": "Certifique-se de verificar sua caixa de entrada de spam.",
|
||||
"step3-message": "Quase lá!",
|
||||
"step4-message": "Guarde o seu Kit de Emergência",
|
||||
"step4-description1": "Se sua conta for bloqueada, seu Kit de emergência é a única maneira de fazer login.",
|
||||
"step4-description2": "Recomendamos que você faça o download e guarde-o em algum lugar seguro.",
|
||||
"step4-description3": "Ele contém sua chave secreta que não podemos acessar ou recuperar para você se você a perder.",
|
||||
"step4-download": "Baixar PDF"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"title": "Kayıt olun",
|
||||
"og-title": "Tek satır kodla .env dosyalarını değiştirin. 3 dakika içerinde Infisical'a kayıt olun.",
|
||||
"og-description": "Infisical takımların API anahtarlarını ve ortam değişkenlerini yönetmelerini ve senkronize etmelerini sağlayan basit, uçtan uca şifrelenmiş bir platformdur. Node.js, Next.js, Gatsby, Nest.js ve daha fazlası ile çalışır.",
|
||||
"signup": "Kayıt ol",
|
||||
"already-have-account": "Hesabın var mı? Giriş yap",
|
||||
"forgot-password": "Şifreni mi unuttun?",
|
||||
"verify": "Doğrula",
|
||||
"step1-start": "Hadi başlayalım",
|
||||
"step1-privacy": "Hesap oluşturarak, Şartlarımızı ve Gizlilik Politikasını okuyup kabul etmiş olursunuz.",
|
||||
"step1-submit": "Başla",
|
||||
"step2-message": "Şu adrese bir doğrulama maili yolladık",
|
||||
"step2-code-error": "Tüh. Kodun hatalı. Kalan denemeler:",
|
||||
"step2-resend-alert": "Kodu ulaşamadı mı?",
|
||||
"step2-resend-submit": "Tekrar Yolla",
|
||||
"step2-resend-progress": "Tekrar yollanıyor...",
|
||||
"step2-spam-alert": "Spam kutunuzu kontrol ettiğinizden emin olun.",
|
||||
"step3-message": "Çok az kaldı!",
|
||||
"step4-message": "Acil Durum Kitinizi kayıt edin",
|
||||
"step4-description1": "Eğer hesabınıza erişemezseniz, Acil Durum Kitiniz giriş yapmanın tek yoludur.",
|
||||
"step4-description2": "Bunu indirmenizi ve güvenli bir ortamda saklamanızı öneriyoruz.",
|
||||
"step4-description3": "Kaybetmeniz durumunda bizim dahi erişemeyeceğimiz veya kurtaramayacağımız Gizli Anahtarınızı barındırır.",
|
||||
"step4-download": "PDF'yi indir",
|
||||
"step5-send-invites": "Davetleri yolla",
|
||||
"step5-invite-team": "Takımını davet et",
|
||||
"step5-subtitle": "Infisical takım arkadaşlarınız ile kullanılmak üzere yapılmıştır. Birlikte test etmek için onları davet edin.",
|
||||
"step5-skip": "Atla"
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
"step1-submit": "Başla",
|
||||
"step2-message": "Şu adrese bir doğrulama maili yolladık",
|
||||
"step2-code-error": "Tüh. Kodun hatalı. Lütfen tekrar dene.",
|
||||
"step2-resend-alert": "Mail ulaşamadı mı?",
|
||||
"step2-resend-alert": "Kodu ulaşamadı mı?",
|
||||
"step2-resend-submit": "Tekrar Yolla",
|
||||
"step2-resend-progress": "Tekrar yollanıyor...",
|
||||
"step2-spam-alert": "Spam kutunuzu kontrol ettiğinizden emin olun.",
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import React, { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { faWarning } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import Button from '@app/components/basic/buttons/Button';
|
||||
import Error from '@app/components/basic/Error';
|
||||
import InputField from '@app/components/basic/InputField';
|
||||
import attemptLogin from '@app/components/utilities/attemptLogin';
|
||||
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
|
||||
|
||||
/**
|
||||
* 1st step of login - user enters their username and password
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.email - email of user
|
||||
* @param {Function} obj.setEmail - function to set the email of user
|
||||
* @param {String} obj.password - password of user
|
||||
* @param {String} obj.setPassword - function to set the password of user
|
||||
* @param {Function} obj.setStep - function to set the login flow step
|
||||
* @returns
|
||||
*/
|
||||
export default function LoginStep ({
|
||||
email,
|
||||
setEmail,
|
||||
password,
|
||||
setPassword,
|
||||
setStep
|
||||
}: {
|
||||
email: string;
|
||||
setEmail: (email: string) => void;
|
||||
password: string;
|
||||
setPassword: (password: string) => void;
|
||||
setStep: (step: number) => void;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loginError, setLoginError] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
if (!email || !password) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
const isLoginSuccessful = await attemptLogin(email, password);
|
||||
if (isLoginSuccessful && isLoginSuccessful.success) {
|
||||
// case: login was successful
|
||||
|
||||
if (isLoginSuccessful.mfaEnabled) {
|
||||
// case: login requires MFA step
|
||||
setStep(2);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// case: login does not require MFA step
|
||||
router.push(`/dashboard/${localStorage.getItem('projectData.id')}`);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
setLoginError(true);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={(e) => e.preventDefault()}>
|
||||
<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-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-6">
|
||||
{t('login:login')}
|
||||
</p>
|
||||
<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={t('common:email')}
|
||||
onChangeHandler={setEmail}
|
||||
type="email"
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
label={t('common:password')}
|
||||
onChangeHandler={setPassword}
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder=""
|
||||
isRequired
|
||||
autoComplete="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">
|
||||
<button
|
||||
type="button"
|
||||
className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5"
|
||||
>
|
||||
{t('login:forgot-password')}
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{!isLoading && loginError && <Error text={t('login:error-login') ?? ''} />}
|
||||
<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
|
||||
type="submit"
|
||||
text={t('login:login') ?? ''}
|
||||
onButtonPressed={async () => handleLogin()}
|
||||
loading={isLoading}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{false && (
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" />
|
||||
{t('common:maintenance-alert')}
|
||||
</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">
|
||||
{t('login:need-account')}
|
||||
</p>
|
||||
<Link href="/signup">
|
||||
<button
|
||||
type="button"
|
||||
className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5"
|
||||
>
|
||||
{t('login:create-account')}
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps = getTranslatedStaticProps(['auth', 'login']);
|
||||
@@ -0,0 +1,158 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import React, { useState } from 'react';
|
||||
import ReactCodeInput from 'react-code-input';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import attemptLoginMfa from '@app/components/utilities/attemptLoginMfa';
|
||||
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
|
||||
import { useSendMfaToken } from '@app/hooks/api/auth';
|
||||
|
||||
import Button from '../basic/buttons/Button';
|
||||
import Error from '../basic/Error';
|
||||
|
||||
// The style for the verification code input
|
||||
const props = {
|
||||
inputStyle: {
|
||||
fontFamily: 'monospace',
|
||||
margin: '4px',
|
||||
MozAppearance: 'textfield',
|
||||
width: '55px',
|
||||
borderRadius: '5px',
|
||||
fontSize: '24px',
|
||||
height: '55px',
|
||||
paddingLeft: '7',
|
||||
backgroundColor: '#0d1117',
|
||||
color: 'white',
|
||||
border: '1px solid #2d2f33',
|
||||
textAlign: 'center',
|
||||
outlineColor: '#8ca542',
|
||||
borderColor: '#2d2f33'
|
||||
}
|
||||
} as const;
|
||||
|
||||
interface VerifyMfaTokenError {
|
||||
response: {
|
||||
data: {
|
||||
context: {
|
||||
code: string;
|
||||
triesLeft: number;
|
||||
}
|
||||
},
|
||||
status: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 2nd step of login - users enter their MFA code
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.email - email of user
|
||||
* @param {String} obj.password - password of user
|
||||
* @param {Function} obj.setStep - function to set the login flow step
|
||||
* @returns
|
||||
*/
|
||||
export default function MFAStep({
|
||||
email,
|
||||
password
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [mfaCode, setMfaCode] = useState('');
|
||||
const [triesLeft, setTriesLeft] = useState<number | undefined>(undefined);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const sendMfaToken = useSendMfaToken();
|
||||
|
||||
const handleLoginMfa = async () => {
|
||||
try {
|
||||
if (mfaCode.length !== 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
const isLoginSuccessful = await attemptLoginMfa({
|
||||
email,
|
||||
password,
|
||||
mfaToken: mfaCode
|
||||
});
|
||||
|
||||
if (isLoginSuccessful) {
|
||||
setIsLoading(false);
|
||||
router.push(`/dashboard/${localStorage.getItem('projectData.id')}`);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
const error = err as VerifyMfaTokenError;
|
||||
|
||||
if (error?.response?.status === 500) {
|
||||
window.location.reload();
|
||||
} else if (error?.response?.data?.context?.triesLeft) {
|
||||
setTriesLeft(error?.response?.data?.context?.triesLeft);
|
||||
if (error.response.data.context.triesLeft === 0) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handleResendMfaCode = async () => {
|
||||
try {
|
||||
await sendMfaToken.mutateAsync({ email });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
|
||||
<p className="text-l flex justify-center text-bunker-300">{t('mfa:step2-message')}</p>
|
||||
<p className="text-l flex justify-center font-semibold my-2 text-bunker-300">{email} </p>
|
||||
<div className="hidden md:block">
|
||||
<ReactCodeInput
|
||||
name=""
|
||||
inputMode="tel"
|
||||
type="text"
|
||||
fields={6}
|
||||
onChange={setMfaCode}
|
||||
{...props}
|
||||
className="mt-6 mb-2"
|
||||
/>
|
||||
</div>
|
||||
{typeof triesLeft === 'number' && <Error text={`${t('mfa:step2-code-error')} ${triesLeft}`} />}
|
||||
<div className="flex max-w-max min-w-28 flex-col items-center justify-center md:p-2 max-h-24 mx-auto text-lg px-4 mt-4 mb-2">
|
||||
<Button
|
||||
text={t('mfa:verify') ?? ''}
|
||||
onButtonPressed={() => handleLoginMfa()}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
|
||||
<div className="flex flex-row items-baseline gap-1 text-sm">
|
||||
<span className="text-bunker-400">{t('mfa:step2-resend-alert')}</span>
|
||||
<u
|
||||
className={`font-normal ${
|
||||
isLoading
|
||||
? 'text-bunker-400'
|
||||
: 'text-primary-700 hover:text-primary duration-200'
|
||||
}`}
|
||||
>
|
||||
<button disabled={isLoading} onClick={() => handleResendMfaCode()} type="button">
|
||||
{isLoading
|
||||
? t('mfa:step2-resend-progress')
|
||||
: t('mfa:step2-resend-submit')}
|
||||
</button>
|
||||
</u>
|
||||
</div>
|
||||
<p className="text-sm text-bunker-400 pb-2">{t('mfa:step2-spam-alert')}</p>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps = getTranslatedStaticProps(['auth', 'mfa']);
|
||||
@@ -87,7 +87,6 @@ export default function Navbar() {
|
||||
}, []);
|
||||
|
||||
const closeApp = async () => {
|
||||
console.log('Logging out...');
|
||||
await logout();
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function DonwloadBackupPDFStep({
|
||||
|
||||
return (
|
||||
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
<p className="text-4xl text-center font-semibold flex justify-center text-primary">
|
||||
{t('signup:step4-message')}
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
|
||||
@@ -42,7 +42,7 @@ export default function DonwloadBackupPDFStep({
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
|
||||
{t('signup:step4-description3')}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
|
||||
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-4 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
|
||||
<Button
|
||||
text="Download PDF"
|
||||
onButtonPressed={async () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
@@ -8,18 +9,21 @@ import nacl from 'tweetnacl';
|
||||
import { encodeBase64 } from 'tweetnacl-util';
|
||||
|
||||
import completeAccountInformationSignup from '@app/pages/api/auth/CompleteAccountInformationSignup';
|
||||
import getOrganizations from '@app/pages/api/organization/getOrgs';
|
||||
import ProjectService from '@app/services/ProjectService';
|
||||
|
||||
import Button from '../basic/buttons/Button';
|
||||
import InputField from '../basic/InputField';
|
||||
import attemptLogin from '../utilities/attemptLogin';
|
||||
import passwordCheck from '../utilities/checks/PasswordCheck';
|
||||
import Aes256Gcm from '../utilities/cryptography/aes-256-gcm';
|
||||
import { deriveArgonKey } from '../utilities/cryptography/crypto';
|
||||
import { saveTokenToLocalStorage } from '../utilities/saveTokenToLocalStorage';
|
||||
import SecurityClient from '../utilities/SecurityClient';
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const client = new jsrp.client();
|
||||
|
||||
interface UserInfoStepProps {
|
||||
verificationToken: string;
|
||||
incrementStep: () => void;
|
||||
email: string;
|
||||
password: string;
|
||||
@@ -44,7 +48,6 @@ interface UserInfoStepProps {
|
||||
* @param {string} obj.setLastName - function managing the state of user's last name
|
||||
*/
|
||||
export default function UserInfoStep({
|
||||
verificationToken,
|
||||
incrementStep,
|
||||
email,
|
||||
password,
|
||||
@@ -62,7 +65,6 @@ export default function UserInfoStep({
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
|
||||
// Verifies if the information that the users entered (name, workspace)
|
||||
// is there, and if the password matches the criteria.
|
||||
@@ -94,17 +96,9 @@ export default function UserInfoStep({
|
||||
const pair = nacl.box.keyPair();
|
||||
const secretKeyUint8Array = pair.secretKey;
|
||||
const publicKeyUint8Array = pair.publicKey;
|
||||
const PRIVATE_KEY = encodeBase64(secretKeyUint8Array);
|
||||
const PUBLIC_KEY = encodeBase64(publicKeyUint8Array);
|
||||
|
||||
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
|
||||
text: PRIVATE_KEY,
|
||||
secret: password
|
||||
.slice(0, 32)
|
||||
.padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), '0')
|
||||
}) as { ciphertext: string; iv: string; tag: string };
|
||||
|
||||
localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY);
|
||||
const privateKey = encodeBase64(secretKeyUint8Array);
|
||||
const publicKey = encodeBase64(publicKeyUint8Array);
|
||||
localStorage.setItem('PRIVATE_KEY', privateKey);
|
||||
|
||||
client.init(
|
||||
{
|
||||
@@ -113,35 +107,90 @@ export default function UserInfoStep({
|
||||
},
|
||||
async () => {
|
||||
client.createVerifier(async (err: any, result: { salt: string; verifier: string }) => {
|
||||
const response = await completeAccountInformationSignup({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
organizationName: `${firstName}'s organization`,
|
||||
publicKey: PUBLIC_KEY,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
token: verificationToken
|
||||
});
|
||||
try {
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
if (response.status === 200) {
|
||||
// response = await response.json();
|
||||
// TODO: moduralize into KeyService
|
||||
const derivedKey = await deriveArgonKey({
|
||||
password,
|
||||
salt: result.salt,
|
||||
mem: 65536,
|
||||
time: 3,
|
||||
parallelism: 1,
|
||||
hashLen: 32
|
||||
});
|
||||
|
||||
if (!derivedKey) throw new Error('Failed to derive key from password');
|
||||
|
||||
localStorage.setItem('publicKey', PUBLIC_KEY);
|
||||
localStorage.setItem('encryptedPrivateKey', ciphertext);
|
||||
localStorage.setItem('iv', iv);
|
||||
localStorage.setItem('tag', tag);
|
||||
const key = crypto.randomBytes(32);
|
||||
|
||||
// create encrypted private key by encrypting the private
|
||||
// key with the symmetric key [key]
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: privateKey,
|
||||
secret: key
|
||||
});
|
||||
|
||||
// create the protected key by encrypting the symmetric key
|
||||
// [key] with the derived key
|
||||
const {
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: key.toString('hex'),
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
const response = await completeAccountInformationSignup({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
organizationName: `${firstName}'s organization`
|
||||
});
|
||||
|
||||
// unset signup JWT token and set JWT token
|
||||
SecurityClient.setSignupToken('');
|
||||
SecurityClient.setToken(response.token);
|
||||
|
||||
try {
|
||||
await attemptLogin(email, password, () => {}, router, true, false);
|
||||
incrementStep();
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
saveTokenToLocalStorage({
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
const userOrgs = await getOrganizations();
|
||||
const orgId = userOrgs[0]?._id;
|
||||
const project = await ProjectService.initProject({
|
||||
organizationId: orgId,
|
||||
projectName: 'Example Project'
|
||||
});
|
||||
|
||||
localStorage.setItem('orgData.id', orgId);
|
||||
localStorage.setItem('projectData.id', project._id);
|
||||
|
||||
incrementStep();
|
||||
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -153,7 +202,7 @@ export default function UserInfoStep({
|
||||
|
||||
return (
|
||||
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-36 md:mb-16">
|
||||
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
<p className="text-4xl font-bold flex justify-center mb-6 mx-8 md:mx-16 text-primary">
|
||||
{t('signup:step3-message')}
|
||||
</p>
|
||||
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
|
||||
@@ -258,4 +307,4 @@ export default function UserInfoStep({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,19 @@
|
||||
import { getAuthToken, setAuthToken } from '@app/reactQuery';
|
||||
import {
|
||||
getAuthToken,
|
||||
setAuthToken,
|
||||
setMfaTempToken,
|
||||
setSignupTempToken} from '@app/reactQuery';
|
||||
|
||||
// depreciated: go for apiRequest module in config/api
|
||||
export default class SecurityClient {
|
||||
static setSignupToken(tokenStr: string) {
|
||||
setSignupTempToken(tokenStr);
|
||||
}
|
||||
|
||||
static setMfaToken(tokenStr: string) {
|
||||
setMfaTempToken(tokenStr);
|
||||
}
|
||||
|
||||
static setToken(tokenStr: string) {
|
||||
setAuthToken(tokenStr);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import crypto from 'crypto';
|
||||
|
||||
import jsrp from 'jsrp';
|
||||
import { SecretDataProps } from 'public/data/frequentInterfaces';
|
||||
|
||||
import Aes256Gcm from '@app/components/utilities/cryptography/aes-256-gcm';
|
||||
import login1 from '@app/pages/api/auth/Login1';
|
||||
import login2 from '@app/pages/api/auth/Login2';
|
||||
import addSecrets from '@app/pages/api/files/AddSecrets';
|
||||
import getOrganizations from '@app/pages/api/organization/getOrgs';
|
||||
import getOrganizationUserProjects from '@app/pages/api/organization/GetOrgUserProjects';
|
||||
import getUser from '@app/pages/api/user/getUser';
|
||||
import uploadKeys from '@app/pages/api/workspace/uploadKeys';
|
||||
import KeyService from '@app/services/KeyService';
|
||||
|
||||
import { encryptAssymmetric } from './cryptography/crypto';
|
||||
import encryptSecrets from './secrets/encryptSecrets';
|
||||
import Telemetry from './telemetry/Telemetry';
|
||||
import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
|
||||
import SecurityClient from './SecurityClient';
|
||||
@@ -22,212 +14,130 @@ import SecurityClient from './SecurityClient';
|
||||
// eslint-disable-next-line new-cap
|
||||
const client = new jsrp.client();
|
||||
|
||||
interface IsLoginSuccessful {
|
||||
mfaEnabled: boolean;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function logs in the user (whether it's right after signup, or a normal login)
|
||||
* @param {string} email - email of the user logging in
|
||||
* @param {string} password - password of the user logging in
|
||||
* @param {function} setErrorLogin - function that visually dispay an error is something is wrong
|
||||
* @param {*} router
|
||||
* @param {boolean} isSignUp - whether this log in is a part of signup
|
||||
* @param {boolean} isLogin - ?
|
||||
* @returns
|
||||
* Return whether or not login is successful for user with email [email]
|
||||
* and password [password]
|
||||
* @param {string} email - email of user to log in
|
||||
* @param {string} password - password of user to log in
|
||||
*/
|
||||
const attemptLogin = async (
|
||||
email: string,
|
||||
password: string,
|
||||
setErrorLogin: (value: boolean) => void,
|
||||
router: any,
|
||||
isSignUp: boolean,
|
||||
isLogin: boolean
|
||||
) => {
|
||||
try {
|
||||
const telemetry = new Telemetry().getInstance();
|
||||
|
||||
password: string
|
||||
): Promise<IsLoginSuccessful> => {
|
||||
const telemetry = new Telemetry().getInstance();
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password
|
||||
},
|
||||
async () => {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { serverPublicKey, salt } = await login1(email, clientPublicKey);
|
||||
|
||||
client.setSalt(salt);
|
||||
client.setServerPublicKey(serverPublicKey);
|
||||
const clientProof = client.getProof(); // called M1
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
const { token, publicKey, encryptedPrivateKey, iv, tag } = await login2(
|
||||
|
||||
const {
|
||||
mfaEnabled,
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await login2(
|
||||
email,
|
||||
clientProof
|
||||
);
|
||||
|
||||
if (mfaEnabled) {
|
||||
// case: MFA is enabled
|
||||
|
||||
SecurityClient.setToken(token);
|
||||
// set temporary (MFA) JWT token
|
||||
SecurityClient.setMfaToken(token);
|
||||
|
||||
const privateKey = Aes256Gcm.decrypt({
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
secret: password
|
||||
.slice(0, 32)
|
||||
.padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), '0')
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
const userOrgs = await getOrganizations();
|
||||
const userOrgsData = userOrgs.map((org: { _id: string }) => org._id);
|
||||
|
||||
let orgToLogin;
|
||||
if (userOrgsData.includes(localStorage.getItem('orgData.id'))) {
|
||||
orgToLogin = localStorage.getItem('orgData.id');
|
||||
} else {
|
||||
orgToLogin = userOrgsData[0];
|
||||
localStorage.setItem('orgData.id', orgToLogin);
|
||||
}
|
||||
|
||||
let orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId: orgToLogin
|
||||
});
|
||||
|
||||
orgUserProjects = orgUserProjects?.map((project: { _id: string }) => project._id);
|
||||
let projectToLogin;
|
||||
if (orgUserProjects.includes(localStorage.getItem('projectData.id'))) {
|
||||
projectToLogin = localStorage.getItem('projectData.id');
|
||||
} else {
|
||||
try {
|
||||
projectToLogin = orgUserProjects[0];
|
||||
localStorage.setItem('projectData.id', projectToLogin);
|
||||
} catch (error) {
|
||||
console.log('ERROR: User likely has no projects. ', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (email) {
|
||||
telemetry.identify(email);
|
||||
telemetry.capture('User Logged In');
|
||||
}
|
||||
|
||||
if (isSignUp) {
|
||||
const randomBytes = crypto.randomBytes(16).toString('hex');
|
||||
const PRIVATE_KEY = String(localStorage.getItem('PRIVATE_KEY'));
|
||||
|
||||
const myUser = await getUser();
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: randomBytes,
|
||||
publicKey: myUser.publicKey,
|
||||
privateKey: PRIVATE_KEY
|
||||
}) as { ciphertext: string; nonce: string };
|
||||
|
||||
await uploadKeys(projectToLogin, myUser._id, ciphertext, nonce);
|
||||
|
||||
const secretsToBeAdded: SecretDataProps[] = [
|
||||
{
|
||||
pos: 0,
|
||||
key: 'DATABASE_URL',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
value: 'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
|
||||
valueOverride: undefined,
|
||||
comment: 'Secret referencing example',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 1,
|
||||
key: 'DB_USERNAME',
|
||||
value: 'OVERRIDE_THIS',
|
||||
valueOverride: undefined,
|
||||
comment:
|
||||
'Override secrets with personal value',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 2,
|
||||
key: 'DB_PASSWORD',
|
||||
value: 'OVERRIDE_THIS',
|
||||
valueOverride: undefined,
|
||||
comment:
|
||||
'Another secret override',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 3,
|
||||
key: 'DB_USERNAME',
|
||||
value: 'user1234',
|
||||
valueOverride: 'user1234',
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 4,
|
||||
key: 'DB_PASSWORD',
|
||||
value: 'example_password',
|
||||
valueOverride: 'example_password',
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 5,
|
||||
key: 'TWILIO_AUTH_TOKEN',
|
||||
value: 'example_twillio_token',
|
||||
valueOverride: undefined,
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 6,
|
||||
key: 'WEBSITE_URL',
|
||||
value: 'http://localhost:3000',
|
||||
valueOverride: undefined,
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
}
|
||||
];
|
||||
const secrets = await encryptSecrets({
|
||||
secretsToEncrypt: secretsToBeAdded,
|
||||
workspaceId: String(localStorage.getItem('projectData.id')),
|
||||
env: 'dev'
|
||||
resolve({
|
||||
mfaEnabled,
|
||||
success: true
|
||||
});
|
||||
await addSecrets({
|
||||
secrets: secrets ?? [],
|
||||
env: 'dev',
|
||||
workspaceId: String(localStorage.getItem('projectData.id'))
|
||||
} else if (
|
||||
!mfaEnabled &&
|
||||
encryptionVersion &&
|
||||
encryptedPrivateKey &&
|
||||
iv &&
|
||||
tag &&
|
||||
token
|
||||
) {
|
||||
// case: MFA is not enabled
|
||||
|
||||
// set JWT token
|
||||
SecurityClient.setToken(token);
|
||||
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
// TODO: in the future - move this logic elsewhere
|
||||
// because this function is about logging the user in
|
||||
// and not initializing the login details
|
||||
const userOrgs = await getOrganizations();
|
||||
const orgId = userOrgs[0]._id;
|
||||
localStorage.setItem('orgData.id', orgId);
|
||||
|
||||
const orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId
|
||||
});
|
||||
|
||||
if (orgUserProjects.length > 0) {
|
||||
localStorage.setItem('projectData.id', orgUserProjects[0]._id);
|
||||
}
|
||||
|
||||
if (email) {
|
||||
telemetry.identify(email);
|
||||
telemetry.capture('User Logged In');
|
||||
}
|
||||
|
||||
resolve({
|
||||
mfaEnabled: false,
|
||||
success: true
|
||||
});
|
||||
}
|
||||
|
||||
if (isLogin) {
|
||||
if (localStorage.getItem('projectData.id') !== "undefined") {
|
||||
router.push(`/dashboard/${localStorage.getItem('projectData.id')}`);
|
||||
} else {
|
||||
router.push("/noprojects");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
setErrorLogin(true);
|
||||
console.log('Login response not available');
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log('Something went wrong during authentication');
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
export default attemptLogin;
|
||||
export default attemptLogin;
|
||||
@@ -0,0 +1,103 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import jsrp from 'jsrp';
|
||||
|
||||
import login1 from '@app/pages/api/auth/Login1';
|
||||
import verifyMfaToken from '@app/pages/api/auth/verifyMfaToken';
|
||||
import getOrganizations from '@app/pages/api/organization/getOrgs';
|
||||
import getOrganizationUserProjects from '@app/pages/api/organization/GetOrgUserProjects';
|
||||
import KeyService from '@app/services/KeyService';
|
||||
|
||||
import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
|
||||
import SecurityClient from './SecurityClient';
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const client = new jsrp.client();
|
||||
|
||||
/**
|
||||
* Return whether or not MFA-login is successful for user with email [email]
|
||||
* and MFA token [mfaToken]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.email - email of user
|
||||
* @param {String} obj.mfaToken - MFA code/token
|
||||
*/
|
||||
const attemptLoginMfa = async ({
|
||||
email,
|
||||
password,
|
||||
mfaToken
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
mfaToken: string;
|
||||
}): Promise<Boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init({
|
||||
username: email,
|
||||
password
|
||||
}, async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1(email, clientPublicKey);
|
||||
|
||||
const {
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await verifyMfaToken({
|
||||
email,
|
||||
mfaToken
|
||||
});
|
||||
|
||||
// unset temporary (MFA) JWT token and set JWT token
|
||||
SecurityClient.setMfaToken('');
|
||||
SecurityClient.setToken(token);
|
||||
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
// TODO: in the future - move this logic elsewhere
|
||||
// because this function is about logging the user in
|
||||
// and not initializing the login details
|
||||
const userOrgs = await getOrganizations();
|
||||
const orgId = userOrgs[0]._id;
|
||||
localStorage.setItem('orgData.id', orgId);
|
||||
|
||||
const orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId
|
||||
});
|
||||
localStorage.setItem('projectData.id', orgUserProjects[0]._id);
|
||||
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default attemptLoginMfa;
|
||||
@@ -9,14 +9,14 @@ const BLOCK_SIZE_BYTES = 16; // 128 bit
|
||||
|
||||
interface EncryptProps {
|
||||
text: string;
|
||||
secret: string;
|
||||
secret: string | Buffer;
|
||||
}
|
||||
|
||||
interface DecryptProps {
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
secret: string;
|
||||
secret: string | Buffer;
|
||||
}
|
||||
|
||||
interface EncryptOutputProps {
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
/* eslint-disable new-cap */
|
||||
import crypto from 'crypto';
|
||||
|
||||
import jsrp from 'jsrp';
|
||||
|
||||
import changePassword2 from '@app/pages/api/auth/ChangePassword2';
|
||||
import SRP1 from '@app/pages/api/auth/SRP1';
|
||||
|
||||
import { saveTokenToLocalStorage } from '../saveTokenToLocalStorage';
|
||||
import Aes256Gcm from './aes-256-gcm';
|
||||
import { deriveArgonKey } from './crypto';
|
||||
|
||||
const clientOldPassword = new jsrp.client();
|
||||
const clientNewPassword = new jsrp.client();
|
||||
|
||||
// TODO: modify this function
|
||||
|
||||
/**
|
||||
* This function loggs in the user (whether it's right after signup, or a normal login)
|
||||
* @param {*} email
|
||||
@@ -63,43 +69,75 @@ const changePassword = async (
|
||||
},
|
||||
async () => {
|
||||
clientNewPassword.createVerifier(async (err, result) => {
|
||||
// The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø)
|
||||
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
|
||||
|
||||
const derivedKey = await deriveArgonKey({
|
||||
password: newPassword,
|
||||
salt: result.salt,
|
||||
mem: 65536,
|
||||
time: 3,
|
||||
parallelism: 1,
|
||||
hashLen: 32
|
||||
});
|
||||
|
||||
if (!derivedKey) throw new Error('Failed to derive key from password');
|
||||
|
||||
const key = crypto.randomBytes(32);
|
||||
|
||||
// create encrypted private key by encrypting the private
|
||||
// key with the symmetric key [key]
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: localStorage.getItem('PRIVATE_KEY') as string,
|
||||
secret: newPassword
|
||||
.slice(0, 32)
|
||||
.padStart(
|
||||
32 + (newPassword.slice(0, 32).length - new Blob([newPassword]).size),
|
||||
'0'
|
||||
)
|
||||
secret: key
|
||||
});
|
||||
|
||||
// create the protected key by encrypting the symmetric key
|
||||
// [key] with the derived key
|
||||
const {
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: key.toString('hex'),
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
if (ciphertext) {
|
||||
localStorage.setItem('encryptedPrivateKey', ciphertext);
|
||||
localStorage.setItem('iv', iv);
|
||||
localStorage.setItem('tag', tag);
|
||||
let res;
|
||||
try {
|
||||
res = await changePassword2({
|
||||
clientProof,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag,
|
||||
});
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await changePassword2({
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
clientProof
|
||||
});
|
||||
if (res && res.status === 400) {
|
||||
setCurrentPasswordError(true);
|
||||
} else if (res && res.status === 200) {
|
||||
setPasswordChanged(true);
|
||||
setCurrentPassword('');
|
||||
setNewPassword('');
|
||||
}
|
||||
} catch (error) {
|
||||
if (res && res.status === 400) {
|
||||
setCurrentPasswordError(true);
|
||||
console.log(error);
|
||||
} else if (res && res.status === 200) {
|
||||
setPasswordChanged(true);
|
||||
setCurrentPassword('');
|
||||
setNewPassword('');
|
||||
}
|
||||
} catch (error) {
|
||||
setCurrentPasswordError(true);
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import argon2 from 'argon2-browser';
|
||||
|
||||
import aes from './aes-256-gcm';
|
||||
|
||||
const nacl = require('tweetnacl');
|
||||
@@ -9,6 +11,50 @@ type EncryptAsymmetricProps = {
|
||||
privateKey: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Derive a key from password [password] and salt [salt] using Argon2id
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.password - password to derive key from
|
||||
* @param {String} obj.salt - salt to derive key from
|
||||
* @param {Number} obj.mem - used memory, in KiB
|
||||
* @param {Number} obj.time - number of iterations
|
||||
* @param {Number} obj.parallelism - desired parallelism
|
||||
* @param {Number} obj.hashLen - desired hash length (i.e. byte-length of derived key)
|
||||
* @returns
|
||||
*/
|
||||
const deriveArgonKey = async ({
|
||||
password,
|
||||
salt,
|
||||
mem,
|
||||
time,
|
||||
parallelism,
|
||||
hashLen
|
||||
}: {
|
||||
password: string;
|
||||
salt: string;
|
||||
mem: number;
|
||||
time: number;
|
||||
parallelism: number;
|
||||
hashLen: number;
|
||||
}) => {
|
||||
let derivedKey;
|
||||
try {
|
||||
derivedKey = await argon2.hash({
|
||||
pass: password,
|
||||
salt,
|
||||
type: argon2.ArgonType.Argon2id,
|
||||
mem,
|
||||
time,
|
||||
parallelism,
|
||||
hashLen
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
return derivedKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return assymmetrically encrypted [plaintext] using [publicKey] where
|
||||
* [publicKey] likely belongs to the recipient.
|
||||
@@ -138,4 +184,10 @@ const decryptSymmetric = ({ ciphertext, iv, tag, key }: DecryptSymmetricProps):
|
||||
return plaintext;
|
||||
};
|
||||
|
||||
export { decryptAssymmetric, decryptSymmetric, encryptAssymmetric, encryptSymmetric };
|
||||
export {
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric,
|
||||
deriveArgonKey,
|
||||
encryptAssymmetric,
|
||||
encryptSymmetric
|
||||
};
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
interface Props {
|
||||
publicKey: string;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
protectedKeyTag?: string;
|
||||
publicKey?: string;
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
privateKey: string;
|
||||
privateKey?: string;
|
||||
}
|
||||
|
||||
export const saveTokenToLocalStorage = ({
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
@@ -14,11 +20,38 @@ export const saveTokenToLocalStorage = ({
|
||||
privateKey,
|
||||
}: Props) => {
|
||||
try {
|
||||
localStorage.setItem("publicKey", publicKey);
|
||||
localStorage.removeItem("protectedKey");
|
||||
localStorage.removeItem("protectedKeyIV");
|
||||
localStorage.removeItem("protectedKeyTag");
|
||||
localStorage.removeItem("publicKey");
|
||||
localStorage.removeItem("encryptedPrivateKey");
|
||||
localStorage.removeItem("iv");
|
||||
localStorage.removeItem("tag");
|
||||
localStorage.removeItem("PRIVATE_KEY");
|
||||
|
||||
if (protectedKey) {
|
||||
localStorage.setItem("protectedKey", protectedKey);
|
||||
}
|
||||
|
||||
if (protectedKeyIV) {
|
||||
localStorage.setItem("protectedKeyIV", protectedKeyIV);
|
||||
}
|
||||
|
||||
if (protectedKeyTag) {
|
||||
localStorage.setItem("protectedKeyTag", protectedKeyTag);
|
||||
}
|
||||
|
||||
if (publicKey) {
|
||||
localStorage.setItem("publicKey", publicKey);
|
||||
}
|
||||
|
||||
if (privateKey) {
|
||||
localStorage.setItem("PRIVATE_KEY", privateKey);
|
||||
}
|
||||
|
||||
localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
localStorage.setItem("PRIVATE_KEY", privateKey);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { getAuthToken } from '@app/reactQuery';
|
||||
import {
|
||||
getAuthToken,
|
||||
getMfaTempToken,
|
||||
getSignupTempToken} from '@app/reactQuery';
|
||||
|
||||
export const apiRequest = axios.create({
|
||||
baseURL: '/',
|
||||
@@ -10,8 +13,17 @@ export const apiRequest = axios.create({
|
||||
});
|
||||
|
||||
apiRequest.interceptors.request.use((config) => {
|
||||
const signupTempToken = getSignupTempToken();
|
||||
const mfaTempToken = getMfaTempToken();
|
||||
const token = getAuthToken();
|
||||
if (token && config.headers) {
|
||||
|
||||
if (signupTempToken && config.headers) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
config.headers.Authorization = `Bearer ${signupTempToken}`;
|
||||
} else if (mfaTempToken && config.headers) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
config.headers.Authorization = `Bearer ${mfaTempToken}`;
|
||||
} else if (token && config.headers) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import Aes256Gcm from '@app/components/utilities/cryptography/aes-256-gcm';
|
||||
import { deriveArgonKey } from '@app/components/utilities/cryptography/crypto';
|
||||
|
||||
/**
|
||||
* @param {Object} obj
|
||||
* @param {Number} obj.encryptionVersion
|
||||
* @param {String} obj.encryptedPrivateKey
|
||||
* @param {String} obj.iv
|
||||
* @param {String} obj.tag
|
||||
* @param {String} obj.password
|
||||
* @param {String} obj.salt
|
||||
* @param {String} obj.protectedKey
|
||||
* @param {String} obj.protectedKeyIV
|
||||
* @param {String} obj.protectedKeyTag
|
||||
*/
|
||||
const decryptPrivateKeyHelper = async ({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
}: {
|
||||
encryptionVersion: number;
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
password: string;
|
||||
salt: string;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
protectedKeyTag?: string;
|
||||
}) => {
|
||||
let privateKey;
|
||||
try {
|
||||
if (encryptionVersion === 1) {
|
||||
privateKey = Aes256Gcm.decrypt({
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
secret: password
|
||||
.slice(0, 32)
|
||||
.padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), '0')
|
||||
});
|
||||
} else if (encryptionVersion === 2 && protectedKey && protectedKeyIV && protectedKeyTag) {
|
||||
const derivedKey = await deriveArgonKey({
|
||||
password,
|
||||
salt,
|
||||
mem: 65536,
|
||||
time: 3,
|
||||
parallelism: 1,
|
||||
hashLen: 32
|
||||
});
|
||||
|
||||
if (!derivedKey) throw new Error('Failed to generate derived key');
|
||||
|
||||
const key = Aes256Gcm.decrypt({
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag,
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
// decrypt back the private key
|
||||
privateKey = Aes256Gcm.decrypt({
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
secret: Buffer.from(key, 'hex')
|
||||
});
|
||||
} else {
|
||||
throw new Error('Insufficient details to decrypt private key');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Failed to decrypt private key');
|
||||
}
|
||||
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
export { decryptPrivateKeyHelper };
|
||||
@@ -0,0 +1,140 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { encryptAssymmetric } from '@app/components/utilities/cryptography/crypto';
|
||||
import encryptSecrets from '@app/components/utilities/secrets/encryptSecrets';
|
||||
import addSecrets from '@app/pages/api/files/AddSecrets';
|
||||
import getUser from '@app/pages/api/user/getUser';
|
||||
import createWorkspace from "@app/pages/api/workspace/createWorkspace";
|
||||
import uploadKeys from '@app/pages/api/workspace/uploadKeys';
|
||||
|
||||
const secretsToBeAdded = [
|
||||
{
|
||||
pos: 0,
|
||||
key: 'DATABASE_URL',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
value: 'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
|
||||
valueOverride: undefined,
|
||||
comment: 'Secret referencing example',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 1,
|
||||
key: 'DB_USERNAME',
|
||||
value: 'OVERRIDE_THIS',
|
||||
valueOverride: undefined,
|
||||
comment:
|
||||
'Override secrets with personal value',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 2,
|
||||
key: 'DB_PASSWORD',
|
||||
value: 'OVERRIDE_THIS',
|
||||
valueOverride: undefined,
|
||||
comment:
|
||||
'Another secret override',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 3,
|
||||
key: 'DB_USERNAME',
|
||||
value: 'user1234',
|
||||
valueOverride: 'user1234',
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 4,
|
||||
key: 'DB_PASSWORD',
|
||||
value: 'example_password',
|
||||
valueOverride: 'example_password',
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 5,
|
||||
key: 'TWILIO_AUTH_TOKEN',
|
||||
value: 'example_twillio_token',
|
||||
valueOverride: undefined,
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 6,
|
||||
key: 'WEBSITE_URL',
|
||||
value: 'http://localhost:3000',
|
||||
valueOverride: undefined,
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Create and initialize a new project in organization with id [organizationId]
|
||||
* Note: current user should be a member of the organization
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.organizationId - id of organization
|
||||
* @param {String} obj.projectName - name of new project
|
||||
* @returns {Project} project - new project
|
||||
*/
|
||||
const initProjectHelper = async ({
|
||||
organizationId,
|
||||
projectName
|
||||
}: {
|
||||
organizationId: string;
|
||||
projectName: string;
|
||||
}) => {
|
||||
let project;
|
||||
try {
|
||||
// create new project
|
||||
project = await createWorkspace({
|
||||
workspaceName: projectName,
|
||||
organizationId
|
||||
});
|
||||
|
||||
// create and upload new (encrypted) project key
|
||||
const randomBytes = crypto.randomBytes(16).toString('hex');
|
||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
||||
|
||||
if (!PRIVATE_KEY) throw new Error('Failed to find private key');
|
||||
|
||||
const user = await getUser();
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: randomBytes,
|
||||
publicKey: user.publicKey,
|
||||
privateKey: PRIVATE_KEY
|
||||
});
|
||||
|
||||
await uploadKeys(project._id, user._id, ciphertext, nonce);
|
||||
|
||||
// encrypt and upload secrets to new project
|
||||
const secrets = await encryptSecrets({
|
||||
secretsToEncrypt: secretsToBeAdded,
|
||||
workspaceId: project._id,
|
||||
env: 'dev'
|
||||
});
|
||||
|
||||
await addSecrets({
|
||||
secrets: secrets ?? [],
|
||||
env: 'dev',
|
||||
workspaceId: project._id
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('Failed to init project in organization', err);
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
export {
|
||||
initProjectHelper
|
||||
}
|
||||
@@ -1 +1,4 @@
|
||||
export { useGetAuthToken } from './queries';
|
||||
export {
|
||||
useGetAuthToken,
|
||||
useSendMfaToken,
|
||||
useVerifyMfaToken} from './queries'
|
||||
|
||||
@@ -1,14 +1,39 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { apiRequest } from '@app/config/request';
|
||||
import { setAuthToken } from '@app/reactQuery';
|
||||
|
||||
import { GetAuthTokenAPI } from './types';
|
||||
import {
|
||||
GetAuthTokenAPI,
|
||||
SendMfaTokenDTO,
|
||||
VerifyMfaTokenDTO,
|
||||
VerifyMfaTokenRes} from './types';
|
||||
|
||||
const authKeys = {
|
||||
getAuthToken: ['token'] as const
|
||||
};
|
||||
|
||||
export const useSendMfaToken = () => {
|
||||
return useMutation<{}, {}, SendMfaTokenDTO>({
|
||||
mutationFn: async ({ email }) => {
|
||||
const { data } = await apiRequest.post('/api/v2/auth/mfa/send', { email });
|
||||
return data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const useVerifyMfaToken = () => {
|
||||
return useMutation<VerifyMfaTokenRes, {}, VerifyMfaTokenDTO>({
|
||||
mutationFn: async ({ email, mfaCode }) => {
|
||||
const { data } = await apiRequest.post('/api/v2/auth/mfa/verify', {
|
||||
email,
|
||||
mfaToken: mfaCode
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh token is set as cookie when logged in
|
||||
// Using that we fetch the auth bearer token needed for auth calls
|
||||
const fetchAuthToken = async () => {
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
export type GetAuthTokenAPI = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export type SendMfaTokenDTO = {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export type VerifyMfaTokenDTO = {
|
||||
email: string;
|
||||
mfaCode: string;
|
||||
}
|
||||
|
||||
export type VerifyMfaTokenRes = {
|
||||
encryptionVersion: number;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
protectedKeyTag?: string;
|
||||
token: string;
|
||||
publicKey: string;
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
}
|
||||
@@ -1,15 +1,23 @@
|
||||
import { UserWsKeyPair } from '../keys/types';
|
||||
|
||||
export type User = {
|
||||
seenIps: string[];
|
||||
_id: string;
|
||||
email: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
__v: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
encryptionVersion?: number;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
protectedKeyTag?: string;
|
||||
publicKey: string;
|
||||
encryptedPrivateKey?: string;
|
||||
iv?: string;
|
||||
tag?: string;
|
||||
isMfaEnabled: boolean;
|
||||
seenIps: string[];
|
||||
_id: string;
|
||||
__v: number;
|
||||
};
|
||||
|
||||
export type OrgUser = {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { useLeaveConfirm } from './useLeaveConfirm';
|
||||
export { usePopUp } from './usePopUp';
|
||||
export { useToggle } from './useToggle';
|
||||
export { useLeaveConfirm } from './useLeaveConfirm';
|
||||
|
||||
@@ -93,6 +93,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
// Placing the localstorage as much as possible
|
||||
// Wait till tony integrates the azure and its launched
|
||||
useEffect(() => {
|
||||
|
||||
// Put a user in a workspace if they're not in one yet
|
||||
const putUserInWorkSpace = async () => {
|
||||
if (tempLocalStorage('orgData.id') === '') {
|
||||
|
||||
@@ -75,8 +75,18 @@ export const Navbar = () => {
|
||||
|
||||
const closeApp = async () => {
|
||||
try {
|
||||
console.log('Logging out...');
|
||||
console.log('Logging out...')
|
||||
await logout.mutateAsync();
|
||||
localStorage.removeItem('protectedKey');
|
||||
localStorage.removeItem('protectedKeyIV');
|
||||
localStorage.removeItem('protectedKeyTag');
|
||||
localStorage.removeItem('publicKey');
|
||||
localStorage.removeItem('encryptedPrivateKey');
|
||||
localStorage.removeItem('iv');
|
||||
localStorage.removeItem('tag');
|
||||
localStorage.removeItem('PRIVATE_KEY');
|
||||
localStorage.removeItem('orgData.id');
|
||||
localStorage.removeItem('projectData.id');
|
||||
router.push('/login');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
|
||||
interface Props {
|
||||
clientProof: string;
|
||||
protectedKey: string;
|
||||
protectedKeyIV: string;
|
||||
protectedKeyTag: string;
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
encryptedPrivateKeyIV: string;
|
||||
encryptedPrivateKeyTag: string;
|
||||
salt: string;
|
||||
verifier: string;
|
||||
clientProof: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -14,7 +17,17 @@ interface Props {
|
||||
* @param {*} clientPublicKey
|
||||
* @returns
|
||||
*/
|
||||
const changePassword2 = ({ encryptedPrivateKey, iv, tag, salt, verifier, clientProof }: Props) =>
|
||||
const changePassword2 = ({
|
||||
clientProof,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt,
|
||||
verifier
|
||||
}: Props) =>
|
||||
SecurityClient.fetchCall('/api/v1/password/change-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -22,9 +35,12 @@ const changePassword2 = ({ encryptedPrivateKey, iv, tag, salt, verifier, clientP
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientProof,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt,
|
||||
verifier
|
||||
})
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
interface Props {
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
protectedKey: string;
|
||||
protectedKeyIV: string;
|
||||
protectedKeyTag: string;
|
||||
publicKey: string;
|
||||
ciphertext: string;
|
||||
encryptedPrivateKey: string;
|
||||
encryptedPrivateKeyIV: string;
|
||||
encryptedPrivateKeyTag: string;
|
||||
organizationName: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
salt: string;
|
||||
verifier: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -19,6 +24,9 @@ interface Props {
|
||||
* @param {string} obj.email - email of the user completing signup
|
||||
* @param {string} obj.firstName - first name of the user completing signup
|
||||
* @param {string} obj.lastName - last name of the user completing sign up
|
||||
* @param {string} obj.protectedKey - protected key in encryption version 2
|
||||
* @param {string} obj.protectedKeyIV - IV of protected key in encryption version 2
|
||||
* @param {string} obj.protectedKeyTag - tag of protected key in encryption version 2
|
||||
* @param {string} obj.organizationName - organization name for this user (usually, [FIRST_NAME]'s organization)
|
||||
* @param {string} obj.publicKey - public key of the user completing signup
|
||||
* @param {string} obj.ciphertext
|
||||
@@ -26,39 +34,40 @@ interface Props {
|
||||
* @param {string} obj.tag
|
||||
* @param {string} obj.salt
|
||||
* @param {string} obj.verifier
|
||||
* @param {string} obj.token - token that confirms a user's identity
|
||||
* @returns
|
||||
*/
|
||||
const completeAccountInformationSignup = ({
|
||||
const completeAccountInformationSignup = async ({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
organizationName,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt,
|
||||
verifier,
|
||||
token
|
||||
}: Props) => fetch('/api/v1/signup/complete-account/signup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${ token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
publicKey,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
organizationName,
|
||||
iv,
|
||||
tag,
|
||||
salt,
|
||||
verifier
|
||||
})
|
||||
organizationName
|
||||
}: Props) => {
|
||||
const { data } = await apiRequest.post('/api/v2/signup/complete-account/signup', {
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt,
|
||||
verifier,
|
||||
organizationName
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export default completeAccountInformationSignup;
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
interface Props {
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
protectedKey: string;
|
||||
protectedKeyIV: string;
|
||||
protectedKeyTag: string;
|
||||
publicKey: string;
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
encryptedPrivateKey: string;
|
||||
encryptedPrivateKeyIV: string;
|
||||
encryptedPrivateKeyTag: string;
|
||||
salt: string;
|
||||
verifier: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
// missing token?
|
||||
// TODO: add to SecurityClient
|
||||
|
||||
|
||||
/**
|
||||
* This function is called in the end of the signup process.
|
||||
* It sends all the necessary nformation to the server.
|
||||
@@ -27,34 +35,36 @@ interface Props {
|
||||
* @param {string} obj.token - token that confirms a user's identity
|
||||
* @returns
|
||||
*/
|
||||
const completeAccountInformationSignupInvite = ({
|
||||
const completeAccountInformationSignupInvite = async ({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt,
|
||||
verifier,
|
||||
token
|
||||
}: Props) => fetch('/api/v1/signup/complete-account/invite', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${ token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
publicKey,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt,
|
||||
verifier
|
||||
})
|
||||
verifier
|
||||
}: Props) => {
|
||||
const { data } = await apiRequest.post('/api/v2/signup/complete-account/invite', {
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt,
|
||||
verifier
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export default completeAccountInformationSignupInvite;
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Login1 {
|
||||
* @returns
|
||||
*/
|
||||
const login1 = async (email: string, clientPublicKey: string) => {
|
||||
const response = await fetch("/api/v1/auth/login1", {
|
||||
const response = await fetch("/api/v2/auth/login1", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -25,7 +25,7 @@ const login1 = async (email: string, clientPublicKey: string) => {
|
||||
const data = (await response.json()) as unknown as Login1;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
throw new Error("Wrong password");
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
interface Login2Response {
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
publicKey: string;
|
||||
tag: string;
|
||||
mfaEnabled: boolean;
|
||||
token: string;
|
||||
encryptionVersion?: number;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
protectedKeyTag?: string;
|
||||
publicKey?: string;
|
||||
encryptedPrivateKey?: string;
|
||||
iv?: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -13,7 +18,7 @@ interface Login2Response {
|
||||
* @returns
|
||||
*/
|
||||
const login2 = async (email: string, clientProof: string) => {
|
||||
const response = await fetch('/api/v1/auth/login2', {
|
||||
const response = await fetch('/api/v2/auth/login2', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
@@ -4,27 +4,38 @@ import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
* This route logs the user out. Note: the user should authorized to do this.
|
||||
* We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry
|
||||
*/
|
||||
const logout = async () =>
|
||||
SecurityClient.fetchCall('/api/v1/auth/logout', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'include'
|
||||
}).then((res) => {
|
||||
const logout = async () => {
|
||||
try {
|
||||
const res = await SecurityClient.fetchCall('/api/v1/auth/logout', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (res?.status === 200) {
|
||||
SecurityClient.setToken('');
|
||||
// Delete the cookie by not setting a value; Alternatively clear the local storage
|
||||
localStorage.setItem('publicKey', '');
|
||||
localStorage.setItem('encryptedPrivateKey', '');
|
||||
localStorage.setItem('iv', '');
|
||||
localStorage.setItem('tag', '');
|
||||
localStorage.setItem('PRIVATE_KEY', '');
|
||||
console.log('User logged out', res);
|
||||
localStorage.removeItem('protectedKey');
|
||||
localStorage.removeItem('protectedKeyIV');
|
||||
localStorage.removeItem('protectedKeyTag');
|
||||
localStorage.removeItem('publicKey');
|
||||
localStorage.removeItem('encryptedPrivateKey');
|
||||
localStorage.removeItem('iv');
|
||||
localStorage.removeItem('tag');
|
||||
localStorage.removeItem('PRIVATE_KEY');
|
||||
localStorage.removeItem('orgData.id');
|
||||
localStorage.removeItem('projectData.id');
|
||||
|
||||
return res;
|
||||
}
|
||||
console.log('Failed to log out');
|
||||
return undefined;
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.log('Error logging out', error);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export default logout;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
interface Props {
|
||||
verificationToken: string;
|
||||
protectedKey: string;
|
||||
protectedKeyIV: string;
|
||||
protectedKeyTag: string;
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
encryptedPrivateKeyIV: string;
|
||||
encryptedPrivateKeyTag: string;
|
||||
salt: string;
|
||||
verifier: string;
|
||||
verificationToken: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -19,22 +22,28 @@ interface Props {
|
||||
* @returns
|
||||
*/
|
||||
const resetPasswordOnAccountRecovery = ({
|
||||
verificationToken,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt,
|
||||
verifier
|
||||
verifier,
|
||||
verificationToken,
|
||||
}: Props) => fetch('/api/v1/password/password-reset', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${ verificationToken}`
|
||||
Authorization: `Bearer ${verificationToken}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt,
|
||||
verifier
|
||||
})
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
/**
|
||||
* Verify MFA token [mfaToken] for user with email [email]
|
||||
* @param {object} obj
|
||||
* @param {string} obj.email - email of user
|
||||
* @param {string} obj.mfaToken - MFA cod/token to verify
|
||||
* @returns
|
||||
*/
|
||||
const verifyMfaToken = async ({
|
||||
email,
|
||||
mfaToken
|
||||
}: {
|
||||
email: string;
|
||||
mfaToken: string;
|
||||
}) => {
|
||||
const { data } = await apiRequest.post('/api/v2/auth/mfa/verify', {
|
||||
email,
|
||||
mfaToken
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export default verifyMfaToken;
|
||||
@@ -0,0 +1,32 @@
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
|
||||
interface Props {
|
||||
isMfaEnabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's MFA-enabled status to [isMfaEnabled]
|
||||
* @param {Object} obj
|
||||
* @param {Boolean} obj.isMfaEnabled - whether or not MFA status should be set to enabled or not
|
||||
* @returns {User} user - user with updated MFA-enabled status
|
||||
*/
|
||||
const updateMyMfaEnabled = async ({
|
||||
isMfaEnabled
|
||||
}: Props) =>
|
||||
SecurityClient.fetchCall(`/api/v2/users/me/mfa`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
isMfaEnabled,
|
||||
})
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return (await res.json()).user;
|
||||
}
|
||||
console.log('Failed to update MFA status');
|
||||
return undefined;
|
||||
});
|
||||
|
||||
export default updateMyMfaEnabled;
|
||||
+31
-104
@@ -4,28 +4,23 @@ import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { faWarning } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import Button from '@app/components/basic/buttons/Button';
|
||||
import Error from '@app/components/basic/Error';
|
||||
import InputField from '@app/components/basic/InputField';
|
||||
import ListBox from '@app/components/basic/Listbox';
|
||||
import attemptLogin from '@app/components/utilities/attemptLogin';
|
||||
import LoginStep from '@app/components/login/LoginStep';
|
||||
import MFAStep from '@app/components/login/MFAStep';
|
||||
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
|
||||
import { isLoggedIn } from '@app/reactQuery';
|
||||
|
||||
import getWorkspaces from './api/workspace/getWorkspaces';
|
||||
|
||||
export default function Login() {
|
||||
const router = useRouter();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [errorLogin, setErrorLogin] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isAlreadyLoggedIn, setIsAlreadyLoggedIn] = useState(false);
|
||||
const router = useRouter();
|
||||
const [step, setStep] = useState(1);
|
||||
const { t } = useTranslation();
|
||||
const lang = router.locale ?? 'en';
|
||||
|
||||
|
||||
const setLanguage = async (to: string) => {
|
||||
router.push('/login', '/login', { locale: to });
|
||||
@@ -45,29 +40,34 @@ export default function Login() {
|
||||
}
|
||||
};
|
||||
if (isLoggedIn()) {
|
||||
setIsAlreadyLoggedIn(true);
|
||||
redirectToDashboard();
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* This function check if the user entered the correct credentials and should be allowed to log in.
|
||||
*/
|
||||
const loginCheck = async () => {
|
||||
if (!email || !password) {
|
||||
return;
|
||||
|
||||
const renderStep = (loginStep: number) => {
|
||||
// TODO: add MFA step
|
||||
switch (loginStep) {
|
||||
case 1:
|
||||
return (
|
||||
<LoginStep
|
||||
email={email}
|
||||
setEmail={setEmail}
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
setStep={setStep}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
// TODO: add MFA step
|
||||
return (
|
||||
<MFAStep
|
||||
email={email}
|
||||
password={password}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <div />
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
await attemptLogin(email, password, setErrorLogin, router, false, true).then(() => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
if (isAlreadyLoggedIn) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -84,80 +84,7 @@ export default function Login() {
|
||||
<Image src="/images/biglogo.png" height={90} width={120} alt="long logo" />
|
||||
</div>
|
||||
</Link>
|
||||
<form onChange={() => setErrorLogin(false)} onSubmit={(e) => e.preventDefault()}>
|
||||
<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-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-6">
|
||||
{t('login:login')}
|
||||
</p>
|
||||
<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={t('common:email')}
|
||||
onChangeHandler={setEmail}
|
||||
type="email"
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
label={t('common:password')}
|
||||
onChangeHandler={setPassword}
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder=""
|
||||
isRequired
|
||||
autoComplete="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">
|
||||
<button
|
||||
type="button"
|
||||
className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5"
|
||||
>
|
||||
{t('login:forgot-password')}
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{!isLoading && errorLogin && <Error text={t('login:error-login') ?? ''} />}
|
||||
<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
|
||||
type="submit"
|
||||
text={t('login:login') ?? ''}
|
||||
onButtonPressed={loginCheck}
|
||||
loading={isLoading}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
|
||||
<p className="text-gray-400">I may have <Link href="/login"><u className="text-sky-500 cursor-pointer">forgotten my password.</u></Link></p>
|
||||
</div> */}
|
||||
</div>
|
||||
{false && (
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" />
|
||||
{t('common:maintenance-alert')}
|
||||
</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">
|
||||
{t('login:need-account')}
|
||||
</p>
|
||||
<Link href="/signup">
|
||||
<button
|
||||
type="button"
|
||||
className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5"
|
||||
>
|
||||
{t('login:create-account')}
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
{renderStep(step)}
|
||||
<div className="absolute right-4 top-0 mt-4 flex items-center justify-center">
|
||||
<div className="w-48 mx-auto">
|
||||
<ListBox
|
||||
@@ -173,4 +100,4 @@ export default function Login() {
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps = getTranslatedStaticProps(['auth', 'login']);
|
||||
export const getStaticProps = getTranslatedStaticProps(['auth', 'login', 'mfa']);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -12,6 +14,7 @@ import passwordCheck from '@app/components/utilities/checks/PasswordCheck';
|
||||
import Aes256Gcm from '@app/components/utilities/cryptography/aes-256-gcm';
|
||||
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
|
||||
|
||||
import { deriveArgonKey } from '../components/utilities/cryptography/crypto';
|
||||
import EmailVerifyOnPasswordReset from './api/auth/EmailVerifyOnPasswordReset';
|
||||
import getBackupEncryptedPrivateKey from './api/auth/getBackupEncryptedPrivateKey';
|
||||
import resetPasswordOnAccountRecovery from './api/auth/resetPasswordOnAccountRecovery';
|
||||
@@ -39,6 +42,7 @@ export default function PasswordReset() {
|
||||
const getEncryptedKeyHandler = async () => {
|
||||
try {
|
||||
const result = await getBackupEncryptedPrivateKey({ verificationToken });
|
||||
|
||||
setPrivateKey(
|
||||
Aes256Gcm.decrypt({
|
||||
ciphertext: result.encryptedPrivateKey,
|
||||
@@ -64,14 +68,6 @@ export default function PasswordReset() {
|
||||
});
|
||||
|
||||
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,
|
||||
@@ -79,13 +75,51 @@ export default function PasswordReset() {
|
||||
},
|
||||
async () => {
|
||||
client.createVerifier(async (err: any, result: { salt: string; verifier: string }) => {
|
||||
const response = await resetPasswordOnAccountRecovery({
|
||||
verificationToken,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
const derivedKey = await deriveArgonKey({
|
||||
password: newPassword,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier
|
||||
mem: 65536,
|
||||
time: 3,
|
||||
parallelism: 1,
|
||||
hashLen: 32
|
||||
});
|
||||
|
||||
if (!derivedKey) throw new Error('Failed to derive key from password');
|
||||
|
||||
const key = crypto.randomBytes(32);
|
||||
|
||||
// create encrypted private key by encrypting the private
|
||||
// key with the symmetric key [key]
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: privateKey,
|
||||
secret: key
|
||||
});
|
||||
|
||||
// create the protected key by encrypting the symmetric key
|
||||
// [key] with the derived key
|
||||
const {
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: key.toString('hex'),
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
const response = await resetPasswordOnAccountRecovery({
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
verificationToken
|
||||
});
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
|
||||
@@ -14,6 +14,7 @@ import passwordCheck from '@app/components/utilities/checks/PasswordCheck';
|
||||
import changePassword from '@app/components/utilities/cryptography/changePassword';
|
||||
import issueBackupKey from '@app/components/utilities/cryptography/issueBackupKey';
|
||||
import { getTranslatedServerSideProps } from '@app/components/utilities/withTranslateProps';
|
||||
import { SecuritySection } from '@app/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection';
|
||||
|
||||
import AddApiKeyDialog from '../../../components/basic/dialog/AddApiKeyDialog';
|
||||
import getAPIKeys from '../../api/apiKey/getAPIKeys';
|
||||
@@ -100,6 +101,7 @@ export default function PersonalSettings() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SecuritySection />
|
||||
<div className="bg-white/5 rounded-md px-6 flex flex-col items-start w-full mt-2 mb-8 pt-2">
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div className="flex flex-col w-full">
|
||||
|
||||
@@ -11,6 +11,7 @@ import DownloadBackupPDF from '@app/components/signup/DonwloadBackupPDFStep';
|
||||
import EnterEmailStep from '@app/components/signup/EnterEmailStep';
|
||||
import TeamInviteStep from '@app/components/signup/TeamInviteStep';
|
||||
import UserInfoStep from '@app/components/signup/UserInfoStep';
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
|
||||
|
||||
import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode';
|
||||
@@ -28,7 +29,6 @@ export default function SignUp() {
|
||||
const [codeError, setCodeError] = useState(false);
|
||||
const [step, setStep] = useState(1);
|
||||
const router = useRouter();
|
||||
const [verificationToken, setVerificationToken] = useState('');
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -59,7 +59,8 @@ export default function SignUp() {
|
||||
// Checking if the code matches the email.
|
||||
const response = await checkEmailVerificationCode({ email, code });
|
||||
if (response.status === 200) {
|
||||
setVerificationToken((await response.json()).token);
|
||||
const {token} = await response.json();
|
||||
SecurityClient.setSignupToken(token);
|
||||
setStep(3);
|
||||
} else {
|
||||
setCodeError(true);
|
||||
@@ -94,7 +95,6 @@ export default function SignUp() {
|
||||
/>
|
||||
) : step === 3 ? (
|
||||
<UserInfoStep
|
||||
verificationToken={verificationToken}
|
||||
incrementStep={incrementStep}
|
||||
email={email}
|
||||
password={password}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Image from 'next/image';
|
||||
@@ -17,7 +19,12 @@ import InputField from '@app/components/basic/InputField';
|
||||
import attemptLogin from '@app/components/utilities/attemptLogin';
|
||||
import passwordCheck from '@app/components/utilities/checks/PasswordCheck';
|
||||
import Aes256Gcm from '@app/components/utilities/cryptography/aes-256-gcm';
|
||||
import { deriveArgonKey } from '@app/components/utilities/cryptography/crypto';
|
||||
import issueBackupKey from '@app/components/utilities/cryptography/issueBackupKey';
|
||||
import { saveTokenToLocalStorage } from '@app/components/utilities/saveTokenToLocalStorage';
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
import getOrganizations from '@app/pages/api/organization/getOrgs';
|
||||
import getOrganizationUserProjects from '@app/pages/api/organization/GetOrgUserProjects';
|
||||
|
||||
import completeAccountInformationSignupInvite from './api/auth/CompleteAccountInformationSignupInvite';
|
||||
import verifySignupInvite from './api/auth/VerifySignupInvite';
|
||||
@@ -75,17 +82,10 @@ export default function SignupInvite() {
|
||||
const pair = nacl.box.keyPair();
|
||||
const secretKeyUint8Array = pair.secretKey;
|
||||
const publicKeyUint8Array = pair.publicKey;
|
||||
const PRIVATE_KEY = encodeBase64(secretKeyUint8Array);
|
||||
const PUBLIC_KEY = encodeBase64(publicKeyUint8Array);
|
||||
const privateKey = encodeBase64(secretKeyUint8Array);
|
||||
const publicKey = encodeBase64(publicKeyUint8Array);
|
||||
|
||||
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
|
||||
text: PRIVATE_KEY,
|
||||
secret: password
|
||||
.slice(0, 32)
|
||||
.padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), '0')
|
||||
});
|
||||
|
||||
localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY);
|
||||
localStorage.setItem('PRIVATE_KEY', privateKey);
|
||||
|
||||
client.init(
|
||||
{
|
||||
@@ -94,35 +94,83 @@ export default function SignupInvite() {
|
||||
},
|
||||
async () => {
|
||||
client.createVerifier(async (err, result) => {
|
||||
let response = await completeAccountInformationSignupInvite({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
publicKey: PUBLIC_KEY,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
token: verificationToken
|
||||
});
|
||||
try {
|
||||
const derivedKey = await deriveArgonKey({
|
||||
password,
|
||||
salt: result.salt,
|
||||
mem: 65536,
|
||||
time: 3,
|
||||
parallelism: 1,
|
||||
hashLen: 32
|
||||
});
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
if (!errorCheck && response.status === 200) {
|
||||
response = await response.json();
|
||||
if (!derivedKey) throw new Error('Failed to derive key from password');
|
||||
|
||||
localStorage.setItem('publicKey', PUBLIC_KEY);
|
||||
localStorage.setItem('encryptedPrivateKey', ciphertext);
|
||||
localStorage.setItem('iv', iv);
|
||||
localStorage.setItem('tag', tag);
|
||||
const key = crypto.randomBytes(32);
|
||||
|
||||
// create encrypted private key by encrypting the private
|
||||
// key with the symmetric key [key]
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: privateKey,
|
||||
secret: key
|
||||
});
|
||||
|
||||
// create the protected key by encrypting the symmetric key
|
||||
// [key] with the derived key
|
||||
const {
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: key.toString('hex'),
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
const {
|
||||
token: jwtToken
|
||||
} = await completeAccountInformationSignupInvite({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier
|
||||
});
|
||||
|
||||
// unset temporary signup JWT token and set JWT token
|
||||
SecurityClient.setSignupToken('');
|
||||
SecurityClient.setToken(jwtToken);
|
||||
|
||||
try {
|
||||
await attemptLogin(email, password, setErrorLogin, router, false, false);
|
||||
setStep(3);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
console.log('Error', error);
|
||||
}
|
||||
saveTokenToLocalStorage({
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
const userOrgs = await getOrganizations();
|
||||
|
||||
const orgId = userOrgs[0]._id;
|
||||
localStorage.setItem('orgData.id', orgId);
|
||||
|
||||
setStep(3);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -152,7 +200,7 @@ export default function SignupInvite() {
|
||||
// user will have temp token if doesn't have an account
|
||||
// then continue with account setup workflow
|
||||
if (res?.token) {
|
||||
setVerificationToken(res.token);
|
||||
SecurityClient.setSignupToken(res.token);
|
||||
setStep(2);
|
||||
} else {
|
||||
// user will be redirected to dashboard
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
|
||||
// this is saved in react-query cache
|
||||
export const SIGNUP_TEMP_TOKEN_CACHE_KEY = ['infisical__signup-temp-token'];
|
||||
export const MFA_TEMP_TOKEN_CACHE_KEY = ['infisical__mfa-temp-token'];
|
||||
export const AUTH_TOKEN_CACHE_KEY = ['infisical__auth-token'];
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
@@ -13,9 +15,17 @@ export const queryClient = new QueryClient({
|
||||
});
|
||||
|
||||
// set token in memory cache
|
||||
export const setSignupTempToken = (token: string) =>
|
||||
queryClient.setQueryData(SIGNUP_TEMP_TOKEN_CACHE_KEY, token);
|
||||
|
||||
export const setMfaTempToken = (token: string) =>
|
||||
queryClient.setQueryData(MFA_TEMP_TOKEN_CACHE_KEY, token);
|
||||
|
||||
export const setAuthToken = (token: string) =>
|
||||
queryClient.setQueryData(AUTH_TOKEN_CACHE_KEY, token);
|
||||
|
||||
export const getSignupTempToken = () => queryClient.getQueryData(SIGNUP_TEMP_TOKEN_CACHE_KEY) as string;
|
||||
export const getMfaTempToken = () => queryClient.getQueryData(MFA_TEMP_TOKEN_CACHE_KEY) as string;
|
||||
export const getAuthToken = () => queryClient.getQueryData(AUTH_TOKEN_CACHE_KEY) as string;
|
||||
|
||||
export const isLoggedIn = () => Boolean(getAuthToken());
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { decryptPrivateKeyHelper } from '@app/helpers/key';
|
||||
|
||||
/**
|
||||
* Class to handle key actions
|
||||
*/
|
||||
class KeyService {
|
||||
|
||||
/** Return the user's decrypted private key
|
||||
* @param {Object} obj
|
||||
* @param {Number} obj.encryptionVersion
|
||||
* @param {String} obj.encryptedPrivateKey
|
||||
* @param {String} obj.iv
|
||||
* @param {String} obj.tag
|
||||
* @param {String} obj.password
|
||||
* @param {String} obj.salt
|
||||
* @param {String} obj.protectedKey
|
||||
* @param {String} obj.protectedKeyIV
|
||||
* @param {String} obj.protectedKeyTag
|
||||
* @returns {String} privateKey - decrypted private key
|
||||
*/
|
||||
static async decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
}: {
|
||||
encryptionVersion: number;
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
password: string;
|
||||
salt: string;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
protectedKeyTag?: string;
|
||||
}) {
|
||||
return decryptPrivateKeyHelper({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default KeyService;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { initProjectHelper } from '@app/helpers/project';
|
||||
|
||||
class ProjectService {
|
||||
/**
|
||||
* Create and initialize a new project in organization with id [organizationId]
|
||||
* Note: current user should be a member of the organization
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.organizationId - id of organization
|
||||
* @param {String} obj.projectName - name of new project
|
||||
* @returns {Project} project - new project
|
||||
*/
|
||||
static async initProject({
|
||||
organizationId,
|
||||
projectName
|
||||
}: {
|
||||
organizationId: string;
|
||||
projectName: string
|
||||
}) {
|
||||
return initProjectHelper({
|
||||
organizationId,
|
||||
projectName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ProjectService;
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Checkbox } from '@app/components/v2';
|
||||
|
||||
import { useGetUser } from '../../../../hooks/api';
|
||||
import { User } from '../../../../hooks/api/types';
|
||||
import updateMyMfaEnabled from '../../../../pages/api/user/updateMyMfaEnabled';
|
||||
|
||||
export const SecuritySection = () => {
|
||||
const [isMfaEnabled, setIsMfaEnabled] = useState(false);
|
||||
const { data: user } = useGetUser();
|
||||
|
||||
useEffect(() => {
|
||||
if (user && typeof user.isMfaEnabled !== 'undefined') {
|
||||
setIsMfaEnabled(user.isMfaEnabled);
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const toggleMfa = async (state: boolean) => {
|
||||
try {
|
||||
const newUser: User = await updateMyMfaEnabled({
|
||||
isMfaEnabled: state
|
||||
});
|
||||
|
||||
if (newUser) {
|
||||
setIsMfaEnabled(newUser.isMfaEnabled);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form>
|
||||
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
|
||||
<p className="mb-4 mt-2 text-xl font-semibold">
|
||||
Two-factor Authentication
|
||||
</p>
|
||||
<Checkbox
|
||||
className="data-[state=checked]:bg-primary"
|
||||
id="isTwoFAEnabled"
|
||||
isChecked={isMfaEnabled}
|
||||
onCheckedChange={(state) => {
|
||||
toggleMfa(state as boolean);
|
||||
}}
|
||||
>
|
||||
Enable 2-factor authentication via your personal email.
|
||||
</Checkbox>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { SecuritySection } from './SecuritySection';
|
||||
@@ -314,10 +314,6 @@ export const ProjectSettingsPage = () => {
|
||||
workspaceName={currentWorkspace?.name}
|
||||
onProjectNameChange={onRenameWorkspace}
|
||||
/>
|
||||
<AutoCapitalizationSection
|
||||
workspaceAutoCapitalization={currentWorkspace?.autoCapitalization}
|
||||
onAutoCapitalizationChange={onAutoCapitalizationToggle}
|
||||
/>
|
||||
<CopyProjectIDSection workspaceID={currentWorkspace?._id || ''} />
|
||||
<EnvironmentSection
|
||||
environments={currentWorkspace?.environments || []}
|
||||
@@ -339,6 +335,10 @@ export const ProjectSettingsPage = () => {
|
||||
workspaceName={currentWorkspace?.name || ''}
|
||||
onCreateTag={onCreateWsTag}
|
||||
/>
|
||||
<AutoCapitalizationSection
|
||||
workspaceAutoCapitalization={currentWorkspace?.autoCapitalization}
|
||||
onAutoCapitalizationChange={onAutoCapitalizationToggle}
|
||||
/>
|
||||
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md border-l border-red bg-white/5 px-6 pl-6 pb-4 pt-4">
|
||||
<p className="text-xl font-bold text-red">{t('settings-project:danger-zone')}</p>
|
||||
<p className="text-md mt-2 text-gray-400">{t('settings-project:danger-zone-note')}</p>
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ export const AutoCapitalizationSection = ({
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<form>
|
||||
<div className="mb-2 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
|
||||
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
|
||||
<p className="mb-4 mt-2 text-xl font-semibold">
|
||||
{t('settings-project:auto-capitalization')}
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user