Merge remote-tracking branch 'upstream/main' into feat/39

This commit is contained in:
이강준
2022-11-28 12:24:20 +09:00
174 changed files with 23100 additions and 20563 deletions
+42 -42
View File
@@ -1,62 +1,62 @@
import "../styles/globals.css";
import "@fortawesome/fontawesome-svg-core/styles.css";
import { config } from "@fortawesome/fontawesome-svg-core";
import Layout from "../components/basic/layout";
import RouteGuard from "../components/RouteGuard";
import { publicPaths } from "../const.js";
import { useEffect } from "react";
import { useRouter } from "next/router";
import { initPostHog } from "../components/analytics/posthog";
import { ENV } from "../components/utilities/config";
import { config } from "@fortawesome/fontawesome-svg-core";
import { initPostHog } from "~/components/analytics/posthog";
import Layout from "~/components/basic/layout";
import RouteGuard from "~/components/RouteGuard";
import { publicPaths } from "~/const";
import { ENV } from "~/utilities/config";
import "@fortawesome/fontawesome-svg-core/styles.css";
import "../styles/globals.css";
config.autoAddCss = false;
const App = ({ Component, pageProps, ...appProps }) => {
const router = useRouter();
const posthog = initPostHog();
const router = useRouter();
const posthog = initPostHog();
useEffect(() => {
// Init for auto capturing
const posthog = initPostHog();
useEffect(() => {
// Init for auto capturing
const posthog = initPostHog();
const handleRouteChange = () => {
if (typeof window !== "undefined") {
if (ENV == "production") {
posthog.capture("$pageview");
}
}
};
const handleRouteChange = () => {
if (typeof window !== "undefined") {
if (ENV == "production") {
posthog.capture("$pageview");
}
}
};
router.events.on("routeChangeComplete", handleRouteChange);
router.events.on("routeChangeComplete", handleRouteChange);
return () => {
router.events.off("routeChangeComplete", handleRouteChange);
};
}, [router.events]);
return () => {
router.events.off("routeChangeComplete", handleRouteChange);
};
}, [router.events]);
// If it's one of these routes, don't add the layout (e.g., these routes are external)
if (
publicPaths.includes("/" + appProps.router.pathname.split("/")[1]) ||
!Component.requireAuth
) {
return (
<Component {...pageProps} />
)
}
// If it's one of these routes, don't add the layout (e.g., these routes are external)
if (
publicPaths.includes("/" + appProps.router.pathname.split("/")[1]) ||
!Component.requireAuth
) {
return <Component {...pageProps} />;
}
return (
<RouteGuard>
<Layout>
<Component {...pageProps} />
</Layout>
</RouteGuard>
);
return (
<RouteGuard>
<Layout>
<Component {...pageProps} />
</Layout>
</RouteGuard>
);
};
export default App;
{
/* <Script
/* <Script
src="https://www.googletagmanager.com/gtag/js?id=G-DQ1XLJJGG1"
strategy="afterInteractive"
/>
+30 -24
View File
@@ -1,33 +1,39 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the second step of the change password process (pake)
* @param {*} clientPublicKey
* @returns
*/
const changePassword2 = ({encryptedPrivateKey, iv, tag, salt, verifier, clientProof}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/password/change-password", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"clientProof": clientProof,
"encryptedPrivateKey": encryptedPrivateKey,
"iv": iv,
"tag": tag,
"salt": salt,
"verifier": verifier
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to change the password');
}
})
const changePassword2 = ({
encryptedPrivateKey,
iv,
tag,
salt,
verifier,
clientProof,
}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/password/change-password", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
clientProof: clientProof,
encryptedPrivateKey: encryptedPrivateKey,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier,
}),
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to change the password");
}
});
};
export default changePassword2;
+14 -15
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient.js";
import { PATH } from "../../../const.js";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient.js";
/**
* This function is used to check if the user is authenticated.
@@ -9,19 +9,18 @@ import { PATH } from "../../../const.js";
* @returns
*/
const checkAuth = async (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/auth/checkAuth", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then(res => {
if (res.status == 200) {
return res;
} else {
console.log("Not authorized");
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/auth/checkAuth", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
}).then((res) => {
if (res.status == 200) {
return res;
} else {
console.log("Not authorized");
}
});
};
export default checkAuth;
@@ -1,4 +1,4 @@
import { PATH } from "../../../const.js";
import { PATH } from "~/const";
/**
* This route check the verification code from the email that user just recieved
@@ -7,16 +7,16 @@ import { PATH } from "../../../const.js";
* @returns
*/
const checkEmailVerificationCode = (email, code) => {
return fetch(PATH + "/api/v1/signup/email/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
code: code,
}),
});
return fetch(PATH + "/api/v1/signup/email/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
code: code,
}),
});
};
export default checkEmailVerificationCode;
@@ -1,4 +1,4 @@
import { PATH } from "../../../const";
import { PATH } from "~/const";
/**
* This function is called in the end of the signup process.
@@ -16,37 +16,37 @@ import { PATH } from "../../../const";
* @returns
*/
const completeAccountInformationSignup = ({
email,
firstName,
lastName,
organizationName,
publicKey,
ciphertext,
iv,
tag,
salt,
verifier,
token
email,
firstName,
lastName,
organizationName,
publicKey,
ciphertext,
iv,
tag,
salt,
verifier,
token,
}) => {
return fetch(PATH + "/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,
}),
});
return fetch(PATH + "/api/v1/signup/complete-account/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({
email,
firstName,
lastName,
publicKey,
encryptedPrivateKey: ciphertext,
organizationName,
iv,
tag,
salt,
verifier,
}),
});
};
export default completeAccountInformationSignup;
@@ -1,4 +1,4 @@
import { PATH } from "../../../const";
import { PATH } from "~/const";
/**
* This function is called in the end of the signup process.
@@ -15,35 +15,35 @@ import { PATH } from "../../../const";
* @returns
*/
const completeAccountInformationSignupInvite = ({
email,
firstName,
lastName,
publicKey,
ciphertext,
iv,
tag,
salt,
verifier,
token
email,
firstName,
lastName,
publicKey,
ciphertext,
iv,
tag,
salt,
verifier,
token,
}) => {
return fetch(PATH + "/api/v1/signup/complete-account/invite", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
body: JSON.stringify({
email: email,
firstName: firstName,
lastName: lastName,
publicKey: publicKey,
encryptedPrivateKey: ciphertext,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier
}),
});
return fetch(PATH + "/api/v1/signup/complete-account/invite", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({
email: email,
firstName: firstName,
lastName: lastName,
publicKey: publicKey,
encryptedPrivateKey: ciphertext,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier,
}),
});
};
export default completeAccountInformationSignupInvite;
@@ -1,32 +1,41 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the route that issues a backup private key that will afterwards be added into a pdf
*/
const issueBackupPrivateKey = ({encryptedPrivateKey, iv, tag, salt, verifier, clientProof}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/password/backup-private-key", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"clientProof": clientProof,
"encryptedPrivateKey": encryptedPrivateKey,
"iv": iv,
"tag": tag,
"salt": salt,
"verifier": verifier
}),
})
.then(res => {
if (res.status == 200) {
return res;
} else {
return res;
console.log("Failed to issue the backup key");
}
})
const issueBackupPrivateKey = ({
encryptedPrivateKey,
iv,
tag,
salt,
verifier,
clientProof,
}) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/password/backup-private-key",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
clientProof: clientProof,
encryptedPrivateKey: encryptedPrivateKey,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier,
}),
}
).then((res) => {
if (res.status == 200) {
return res;
} else {
return res;
console.log("Failed to issue the backup key");
}
});
};
export default issueBackupPrivateKey;
+11 -11
View File
@@ -1,4 +1,4 @@
import { PATH } from "../../../const.js";
import { PATH } from "~/const";
/**
* This is the first step of the login process (pake)
@@ -7,16 +7,16 @@ import { PATH } from "../../../const.js";
* @returns
*/
const login1 = (email, clientPublicKey) => {
return fetch(PATH + "/api/v1/auth/login1", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
clientPublicKey,
}),
});
return fetch(PATH + "/api/v1/auth/login1", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
clientPublicKey,
}),
});
};
export default login1;
+19 -20
View File
@@ -1,4 +1,4 @@
import { PATH } from "../../../const";
import { PATH } from "~/const";
/**
* This is the second step of the login process
@@ -7,25 +7,24 @@ import { PATH } from "../../../const";
* @returns
*/
const login2 = (email, clientProof) => {
return fetch(PATH + "/api/v1/auth/login2", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
clientProof,
}),
credentials: "include"
})
.then(res => {
if (res.status == 200) {
console.log("User logged in", res);
return res;
} else {
console.log("Failed to log in");
}
})
return fetch(PATH + "/api/v1/auth/login2", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
clientProof,
}),
credentials: "include",
}).then((res) => {
if (res.status == 200) {
console.log("User logged in", res);
return res;
} else {
console.log("Failed to log in");
}
});
};
export default login2;
+23 -24
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route logs the user out. Note: the user should authorized to do this.
@@ -9,28 +9,27 @@ import { PATH } from "../../../const";
* @returns
*/
const logout = async (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/auth/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include"
})
.then(res => {
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);
return res;
} else {
console.log("Failed to log out");
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/auth/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
}).then((res) => {
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);
return res;
} else {
console.log("Failed to log out");
}
});
};
export default logout;
+18 -19
View File
@@ -1,28 +1,27 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the first step of the change password process (pake)
* @param {*} clientPublicKey
* @returns
*/
const SRP1 = ({clientPublicKey}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/password/srp1", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
clientPublicKey,
}),
})
.then(async res => {
if (res.status == 200) {
return (await res.json());
} else {
console.log('Failed to do the first step of SRP');
}
})
const SRP1 = ({ clientPublicKey }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/password/srp1", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
clientPublicKey,
}),
}).then(async (res) => {
if (res.status == 200) {
return await res.json();
} else {
console.log("Failed to do the first step of SRP");
}
});
};
export default SRP1;
@@ -1,19 +1,19 @@
import { PATH } from "../../../const.js";
import { PATH } from "~/const";
/**
* This route send the verification email to the user's email (contains a 6-digit verification code)
* @param {*} email
*/
const sendVerificationEmail = (email) => {
fetch(PATH + "/api/v1/signup/email/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
});
fetch(PATH + "/api/v1/signup/email/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
});
};
export default sendVerificationEmail;
+14 -15
View File
@@ -1,20 +1,19 @@
import { PATH } from "../../../const.js";
import { PATH } from "~/const";
const token = async (req, res) => {
return fetch(PATH + "/api/v1/auth/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include"
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).token;
} else {
console.log('Getting a new token failed');
}
})
return fetch(PATH + "/api/v1/auth/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).token;
} else {
console.log("Getting a new token failed");
}
});
};
export default token;
+12 -12
View File
@@ -1,4 +1,4 @@
import { PATH } from "../../../const";
import { PATH } from "~/const";
/**
* This route verifies the signup invite link
@@ -6,17 +6,17 @@ import { PATH } from "../../../const";
* @param {*} code
* @returns
*/
const verifySignupInvite = ({email, code}) => {
return fetch(PATH + "/api/v1/invite-org/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
code,
}),
});
const verifySignupInvite = ({ email, code }) => {
return fetch(PATH + "/api/v1/invite-org/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
code,
}),
});
};
export default verifySignupInvite;
@@ -1,4 +1,4 @@
import { PATH } from "../../../const.js";
import { PATH } from "~/const";
/**
* This route lets us get the public key of infisical. Th euser doesn't have to be authenticated since this is just the public key.
@@ -7,12 +7,12 @@ import { PATH } from "../../../const.js";
* @returns
*/
const publicKeyInfisical = (req, res) => {
return fetch(PATH + "/api/v1/key/publicKey/infisical", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
return fetch(PATH + "/api/v1/key/publicKey/infisical", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
};
export default publicKeyInfisical;
+24 -22
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient.js";
import { PATH } from "../../../const.js";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient.js";
/**
* This function fetches the encrypted secrets from the .env file
@@ -8,26 +8,28 @@ import { PATH } from "../../../const.js";
* @returns
*/
const getSecrets = async (workspaceId, env) => {
return SecurityClient.fetchCall(PATH + "/api/v1/secret/" +
workspaceId +
"?" +
new URLSearchParams({
environment: env,
channel: "web",
}
), {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json());
} else {
console.log('Failed to get project secrets');
}
})
return SecurityClient.fetchCall(
PATH +
"/api/v1/secret/" +
workspaceId +
"?" +
new URLSearchParams({
environment: env,
channel: "web",
}),
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return await res.json();
} else {
console.log("Failed to get project secrets");
}
});
};
export default getSecrets;
+20 -21
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function uploads the encrypted .env file
@@ -8,25 +8,24 @@ import { PATH } from "../../../const";
* @returns
*/
const uploadSecrets = async ({ workspaceId, secrets, keys, environment }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/secret/" + workspaceId, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
secrets,
keys,
environment,
channel: "web",
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to push secrets');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/secret/" + workspaceId, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
secrets,
keys,
environment,
channel: "web",
}),
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to push secrets");
}
});
};
export default uploadSecrets;
@@ -1,24 +1,26 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
const changeHerokuConfigVars = ({ integrationId, key, secrets }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration/" + integrationId + "/sync", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
key,
secrets,
})
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to sync secrets to Heroku');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/integration/" + integrationId + "/sync",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
key,
secrets,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to sync secrets to Heroku");
}
});
};
export default changeHerokuConfigVars;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route deletes an integration from a certain project
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteIntegration = ({ integrationId }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration/" + integrationId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).workspace;
} else {
console.log('Failed to delete an integration');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/integration/" + integrationId,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspace;
} else {
console.log("Failed to delete an integration");
}
});
};
export default deleteIntegration;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route deletes an integration authorization from a certain project
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteIntegrationAuth = ({ integrationAuthId }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration-auth/" + integrationAuthId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to delete an integration authorization');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/integration-auth/" + integrationAuthId,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to delete an integration authorization");
}
});
};
export default deleteIntegrationAuth;
@@ -1,20 +1,22 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
const getIntegrationApps = ({ integrationAuthId }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration-auth/" + integrationAuthId + "/apps", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).apps;
} else {
console.log('Failed to get available apps for an integration');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/integration-auth/" + integrationAuthId + "/apps",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).apps;
} else {
console.log("Failed to get available apps for an integration");
}
});
};
export default getIntegrationApps;
@@ -1,20 +1,19 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
const getIntegrations = () => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration/integrations", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).integrations;
} else {
console.log('Failed to get project integrations');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/integration/integrations", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).integrations;
} else {
console.log("Failed to get project integrations");
}
});
};
export default getIntegrations;
@@ -1,32 +1,34 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route starts the integration after teh default one if gonna set up.
* This route starts the integration after teh default one if gonna set up.
* @param {*} integrationId
* @returns
*/
const startIntegration = ({ integrationId, appName, environment }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration/" + integrationId, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"update": {
app: appName,
environment,
isActive: true
}
})
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to start an integration');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/integration/" + integrationId,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
update: {
app: appName,
environment,
isActive: true,
},
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to start an integration");
}
});
};
export default startIntegration;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the first step of the change password process (pake)
@@ -7,24 +7,26 @@ import { PATH } from "../../../const";
* @returns
*/
const AuthorizeIntegration = ({ workspaceId, code, integration }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration-auth/oauth-token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
workspaceId,
code,
integration
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to authorize the integration');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/integration-auth/oauth-token",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
workspaceId,
code,
integration,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to authorize the integration");
}
});
};
export default AuthorizeIntegration;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets authorizations of a certain project (Heroku, etc.)
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaceAuthorizations = ({ workspaceId }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/authorizations", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).authorizations;
} else {
console.log('Failed to get project authorizations');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/authorizations",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).authorizations;
} else {
console.log("Failed to get project authorizations");
}
});
};
export default getWorkspaceAuthorizations;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets integrations of a certain project (Heroku, etc.)
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaceIntegrations = ({ workspaceId }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/integrations", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).integrations;
} else {
console.log('Failed to get the project integrations');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/integrations",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).integrations;
} else {
console.log("Failed to get the project integrations");
}
});
};
export default getWorkspaceIntegrations;
+14 -15
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get info about a certain org
@@ -8,19 +8,18 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganization = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).organization;
} else {
console.log('Failed to get org info');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).organization;
} else {
console.log("Failed to get org info");
}
});
};
export default getOrganization;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get all the users in an org.
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganizationProjects = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/organization/" + req.orgId + "/workspaces", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).workspaces;
} else {
console.log('Failed to get projects for an org');
}
})
return SecurityClient.fetchCall(
PATH + "/api/organization/" + req.orgId + "/workspaces",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspaces;
} else {
console.log("Failed to get projects for an org");
}
});
};
export default getOrganizationProjects;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the current subscription of an org.
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganizationSubscriptions = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId + "/subscriptions", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).subscriptions;
} else {
console.log('Failed to get org subscriptions');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + req.orgId + "/subscriptions",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).subscriptions;
} else {
console.log("Failed to get org subscriptions");
}
});
};
export default getOrganizationSubscriptions;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get all the projects of a certain user in an org.
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganizationUserProjects = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId + "/my-workspaces", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).workspaces;
} else {
console.log('Failed to get projects of a user in an org');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + req.orgId + "/my-workspaces",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspaces;
} else {
console.log("Failed to get projects of a user in an org");
}
});
};
export default getOrganizationUserProjects;
+17 -15
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get all the users in an org.
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganizationUsers = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId + "/users", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).users;
} else {
console.log('Failed to get org users');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + req.orgId + "/users",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).users;
} else {
console.log("Failed to get org users");
}
});
};
export default getOrganizationUsers;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route redirects the user to the right stripe billing page.
@@ -7,20 +7,22 @@ import { PATH } from "../../../const";
* @param {*} res
* @returns
*/
const StripeRedirect = ({orgId}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + orgId + "/customer-portal-session", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return window.location.href = (await res.json()).url;
} else {
console.log('Failed to redirect to Stripe');
}
})
const StripeRedirect = ({ orgId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + orgId + "/customer-portal-session",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (window.location.href = (await res.json()).url);
} else {
console.log("Failed to redirect to Stripe");
}
});
};
export default StripeRedirect;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route add an incident contact email to a certain organization
@@ -7,22 +7,24 @@ import { PATH } from "../../../const";
* @returns
*/
const addIncidentContact = (organizationId, email) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to add an incident contact');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to add an incident contact");
}
});
};
export default addIncidentContact;
+18 -19
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function sends an email invite to a user to join an org
@@ -8,23 +8,22 @@ import { PATH } from "../../../const";
* @returns
*/
const addUserToOrg = (email, orgId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/invite-org/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
inviteeEmail: email,
organizationId: orgId,
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to add a user to an org');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/invite-org/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
inviteeEmail: email,
organizationId: orgId,
}),
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to add a user to an org");
}
});
};
export default addUserToOrg;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route deletes an incident Contact from a certain organization
@@ -7,22 +7,24 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteIncidentContact = (organizaionId, email) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + organizaionId + "/incidentContactOrg", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to delete an incident contact');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + organizaionId + "/incidentContactOrg",
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to delete an incident contact");
}
});
};
export default deleteIncidentContact;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function removes a certain member from a certain organization
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteUserFromOrganization = (membershipId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/membership-org/" + membershipId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to delete a user from an org');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/membership-org/" + membershipId,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to delete a user from an org");
}
});
};
export default deleteUserFromOrganization;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This routes gets all the incident contacts of a certain organization
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getIncidentContacts = (organizationId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).incidentContactsOrg;
} else {
console.log('Failed to get incident contacts');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).incidentContactsOrg;
} else {
console.log("Failed to get incident contacts");
}
});
};
export default getIncidentContacts;
+14 -15
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the all the orgs of a certain user.
@@ -8,19 +8,18 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganizations = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).organizations;
} else {
console.log('Failed to get orgs of a user');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/organization", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).organizations;
} else {
console.log("Failed to get orgs of a user");
}
});
};
export default getOrganizations;
+20 -18
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us rename a certain org.
@@ -8,22 +8,24 @@ import { PATH } from "../../../const";
* @returns
*/
const renameOrg = (orgId, newOrgName) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + orgId + "/name", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: newOrgName,
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to rename an organization');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + orgId + "/name",
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: newOrgName,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to rename an organization");
}
});
};
export default renameOrg;
@@ -1,34 +1,41 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets service tokens for a specific user in a project
* @param {*} param0
* @returns
*/
const addServiceToken = ({name, workspaceId, environment, expiresIn, publicKey, encryptedKey, nonce}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/service-token/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name,
workspaceId,
environment,
expiresIn,
publicKey,
encryptedKey,
nonce
})
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).token;
} else {
console.log('Failed to add service tokens');
}
})
const addServiceToken = ({
name,
workspaceId,
environment,
expiresIn,
publicKey,
encryptedKey,
nonce,
}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/service-token/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name,
workspaceId,
environment,
expiresIn,
publicKey,
encryptedKey,
nonce,
}),
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).token;
} else {
console.log("Failed to add service tokens");
}
});
};
export default addServiceToken;
@@ -1,25 +1,27 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets service tokens for a specific user in a project
* @param {*} param0
* @returns
*/
const getServiceTokens = ({workspaceId}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/service-tokens", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).serviceTokens;
} else {
console.log('Failed to get service tokens');
}
})
const getServiceTokens = ({ workspaceId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/service-tokens",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).serviceTokens;
} else {
console.log("Failed to get service tokens");
}
});
};
export default getServiceTokens;
+14 -15
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets the information about a specific user.
@@ -8,19 +8,18 @@ import { PATH } from "../../../const";
* @returns
*/
const getUser = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/user", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).user;
} else {
console.log('Failed to get user info');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/user", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).user;
} else {
console.log("Failed to get user info");
}
});
};
export default getUser;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route registers a certain action for a user
@@ -8,23 +8,26 @@ import { PATH } from "../../../const";
* @returns
*/
const checkUserAction = ({ action }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/user-action" +
"?" +
new URLSearchParams({
action,
}), {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).userAction;
} else {
console.log('Failed to check a user action');
}
})
return SecurityClient.fetchCall(
PATH +
"/api/v1/user-action" +
"?" +
new URLSearchParams({
action,
}),
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).userAction;
} else {
console.log("Failed to check a user action");
}
});
};
export default checkUserAction;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route registers a certain action for a user
@@ -7,22 +7,21 @@ import { PATH } from "../../../const";
* @returns
*/
const registerUserAction = ({ action }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/user-action", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
action,
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to register a user action');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/user-action", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
action,
}),
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to register a user action");
}
});
};
export default registerUserAction;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function adds a user to a project
@@ -8,22 +8,24 @@ import { PATH } from "../../../const";
* @returns
*/
const addUserToWorkspace = (email, workspaceId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/invite-signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
})
.then(async res => {
if (res.status == 200) {
return (await res.json());
} else {
console.log('Failed to add a user to project');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/invite-signup",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
}
).then(async (res) => {
if (res.status == 200) {
return await res.json();
} else {
console.log("Failed to add a user to project");
}
});
};
export default addUserToWorkspace;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function change the access of a user in a certain workspace
@@ -8,22 +8,24 @@ import { PATH } from "../../../const";
* @returns
*/
const changeUserRoleInWorkspace = (membershipId, role) => {
return SecurityClient.fetchCall(PATH + "/api/v1/membership/" + membershipId + "/change-role", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
role: role,
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to change the user role in a project');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/membership/" + membershipId + "/change-role",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
role: role,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to change the user role in a project");
}
});
};
export default changeUserRoleInWorkspace;
+18 -19
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route creates a new workspace for a user.
@@ -7,23 +7,22 @@ import { PATH } from "../../../const";
* @returns
*/
const createWorkspace = (workspaceName, organizationId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
workspaceName: workspaceName,
organizationId: organizationId,
}),
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).workspace;
} else {
console.log('Failed to create a project');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
workspaceName: workspaceName,
organizationId: organizationId,
}),
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspace;
} else {
console.log("Failed to create a project");
}
});
};
export default createWorkspace;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function removes a certain member from a certain workspace
@@ -7,19 +7,18 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteUserFromWorkspace = (membershipId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/membership/" + membershipId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to delete a user from a project');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/membership/" + membershipId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to delete a user from a project");
}
});
};
export default deleteUserFromWorkspace;
+14 -15
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route deletes a specified workspace.
@@ -7,19 +7,18 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteWorkspace = (workspaceId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to delete a project');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to delete a project");
}
});
};
export default deleteWorkspace;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* Get the latest key pairs from a certain workspace
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getLatestFileKey = (workspaceId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/key/" + workspaceId + "/latest", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json());
} else {
console.log('Failed to get the latest key pairs for a certain project');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/key/" + workspaceId + "/latest",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return await res.json();
} else {
console.log("Failed to get the latest key pairs for a certain project");
}
});
};
export default getLatestFileKey;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the information of a certain project.
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaceInfo = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + req.workspaceId, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).workspace;
} else {
console.log('Failed to get project info');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + req.workspaceId,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspace;
} else {
console.log("Failed to get project info");
}
});
};
export default getWorkspaceInfo;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the public keys of everyone in your workspace.
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaceKeys = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + req.workspaceId + "/keys", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).publicKeys;
} else {
console.log('Failed to get the public keys of everyone in the workspace');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + req.workspaceId + "/keys",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).publicKeys;
} else {
console.log("Failed to get the public keys of everyone in the workspace");
}
});
};
export default getWorkspaceKeys;
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get all the users in the workspace.
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaceUsers = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + req.workspaceId + "/users", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).users;
} else {
console.log('Failed to get Project Users');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + req.workspaceId + "/users",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).users;
} else {
console.log("Failed to get Project Users");
}
});
};
export default getWorkspaceUsers;
+14 -15
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the public keys of everyone in your workspace.
@@ -8,19 +8,18 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaces = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json()).workspaces;
} else {
console.log('Failed to get projects');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspaces;
} else {
console.log("Failed to get projects");
}
});
};
export default getWorkspaces;
+20 -18
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us rename a certain workspace.
@@ -8,22 +8,24 @@ import { PATH } from "../../../const";
* @returns
*/
const renameWorkspace = (workspaceId, newWorkspaceName) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/name", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: newWorkspaceName,
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to rename a project');
}
})
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/name",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: newWorkspaceName,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to rename a project");
}
});
};
export default renameWorkspace;
+21 -22
View File
@@ -1,5 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import { PATH } from "../../../const";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route uplods the keys in an encrypted format.
@@ -10,26 +10,25 @@ import { PATH } from "../../../const";
* @returns
*/
const uploadKeys = (workspaceId, userId, encryptedKey, nonce) => {
return SecurityClient.fetchCall(PATH + "/api/v1/key/" + workspaceId, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
key: {
userId: userId,
encryptedKey: encryptedKey,
nonce: nonce,
},
}),
})
.then(async res => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to upload keys for a new user');
}
})
return SecurityClient.fetchCall(PATH + "/api/v1/key/" + workspaceId, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
key: {
userId: userId,
encryptedKey: encryptedKey,
nonce: nonce,
},
}),
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log("Failed to upload keys for a new user");
}
});
};
export default uploadKeys;
+22 -20
View File
@@ -1,31 +1,33 @@
import React, { useEffect } from "react";
import { useRouter } from "next/router";
import Head from "next/head";
import { useRouter } from "next/router";
import getWorkspaces from "./api/workspace/getWorkspaces";
export default function DashboardRedirect() {
const router = useRouter();
const router = useRouter();
/**
* Here we forward to the default workspace if a user opens this url
*/
useEffect(async () => {
let userWorkspace;
try {
if (localStorage.getItem("projectData.id")) {
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
} else {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id;
router.push("/dashboard/" + userWorkspace);
}
} catch (error) {
console.log("Error - Not logged in yet");
}
}, []);
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
let userWorkspace;
try {
if (localStorage.getItem("projectData.id")) {
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
} else {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id;
router.push("/dashboard/" + userWorkspace);
}
} catch (error) {
console.log("Error - Not logged in yet");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>
return <div></div>;
}
DashboardRedirect.requireAuth = true;
File diff suppressed because it is too large Load Diff
+24 -23
View File
@@ -4,32 +4,33 @@ import { useRouter } from "next/router";
const queryString = require("query-string");
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
export default function Heroku() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
/**
* Here we forward to the default workspace if a user opens this url
*/
useEffect(async () => {
try {
if (state == localStorage.getItem("latestCSRFToken")) {
await AuthorizeIntegration({
workspaceId: localStorage.getItem("projectData.id"),
code,
integration: "heroku"
})
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (error) {
console.log("Error - Not logged in yet");
}
}, []);
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
try {
if (state == localStorage.getItem("latestCSRFToken")) {
await AuthorizeIntegration({
workspaceId: localStorage.getItem("projectData.id"),
code,
integration: "heroku",
});
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (error) {
console.log("Error - Not logged in yet");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>
return <div></div>;
}
Heroku.requireAuth = true;
+6 -8
View File
@@ -2,14 +2,12 @@ import React, { useEffect } from "react";
import { useRouter } from "next/router";
export default function Home() {
const router = useRouter();
const router = useRouter();
useEffect(async () => {
router.push("/login");
}, []);
useEffect(() => {
router.push("/login");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="bg-bunker-800 w-screen">
</div>
);
return <div className="bg-bunker-800 w-screen"></div>;
}
+345 -251
View File
@@ -1,278 +1,372 @@
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import { useRouter } from "next/router";
import {
faArrowRight,
faCheck,
faRotate,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faArrowRight, faRotate, faX } from "@fortawesome/free-solid-svg-icons";
import ListBox from "../../components/basic/Listbox";
import NavHeader from "../../components/navigation/NavHeader";
import getIntegrations from "../api/integrations/GetIntegrations";
import Button from "~/components/basic/buttons/Button";
import ListBox from "~/components/basic/Listbox";
import NavHeader from "~/components/navigation/NavHeader";
import getSecretsForProject from "~/components/utilities/secrets/getSecretsForProject";
import pushKeysIntegration from "~/components/utilities/secrets/pushKeysIntegration";
import guidGenerator from "~/utilities/randomId";
import deleteIntegration from "../api/integrations/DeleteIntegration";
import deleteIntegrationAuth from "../api/integrations/DeleteIntegrationAuth";
import getIntegrationApps from "../api/integrations/GetIntegrationApps";
import getIntegrations from "../api/integrations/GetIntegrations";
import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations";
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations";
import startIntegration from "../api/integrations/StartIntegration";
import deleteIntegration from "../api/integrations/DeleteIntegration";
import getSecretsForProject from "../../components/utilities/getSecretsForProject";
import pushKeysIntegration from "../../components/utilities/pushKeysIntegration";
import deleteIntegrationAuth from "../api/integrations/DeleteIntegrationAuth";
import Button from "../../components/basic/buttons/Button";
import guidGenerator from "../../components/utilities/randomId";
const crypto = require("crypto");
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const reverseEnvMapping = {
"dev": "Development",
"staging": "Staging",
"prod": "Production",
"test": "Testing"
}
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing",
};
const Integration = ({projectIntegration}) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState(reverseEnvMapping[projectIntegration.environment]);
const [fileState, setFileState] = useState([]);
const [data, setData] = useState();
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
const router = useRouter();
const [apps, setApps] = useState([]);
const [integrationApp, setIntegrationApp] = useState(projectIntegration.app ? projectIntegration.app : apps[0]);
const Integration = ({ projectIntegration }) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState(
reverseEnvMapping[projectIntegration.environment]
);
const [fileState, setFileState] = useState([]);
const [data, setData] = useState();
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
const router = useRouter();
const [apps, setApps] = useState([]);
const [integrationApp, setIntegrationApp] = useState(
projectIntegration.app ? projectIntegration.app : apps[0]
);
useEffect(async () => {
const tempHerokuApps = await getIntegrationApps({integrationAuthId: projectIntegration.integrationAuth});
const tempHerokuAppNames = tempHerokuApps.map(app => app.name);
setApps(tempHerokuAppNames);
setIntegrationApp(projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0])
}, []);
useEffect(async () => {
const tempHerokuApps = await getIntegrationApps({
integrationAuthId: projectIntegration.integrationAuth,
});
const tempHerokuAppNames = tempHerokuApps.map((app) => app.name);
setApps(tempHerokuAppNames);
setIntegrationApp(
projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0]
);
}, []);
return (
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
<div className="flex flex-row">
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
ENVIRONMENT
</div>
<ListBox
data={
!projectIntegration.isActive && [
"Development",
"Staging",
"Production",
]
}
selected={integrationEnvironment}
onChange={setIntegrationEnvironment}
/>
</div>
<FontAwesomeIcon
icon={faArrowRight}
className="mx-4 text-gray-400 mt-8"
/>
<div className="mr-2">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
INTEGRATION
</div>
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
{projectIntegration.integration.charAt(0).toUpperCase() +
projectIntegration.integration.slice(1)}
</div>
</div>
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
HEROKU APP
</div>
<ListBox
data={!projectIntegration.isActive && apps}
selected={integrationApp}
onChange={setIntegrationApp}
/>
</div>
</div>
<div className="flex flex-row mt-6">
{projectIntegration.isActive ? (
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
<FontAwesomeIcon
icon={faRotate}
className="text-lg mr-2.5 text-primary animate-spin"
/>
<div className="text-gray-300 font-semibold">In Sync</div>
</div>
) : (
<Button
text="Start Integration"
onButtonPressed={async () => {
const result = await startIntegration({
integrationId: projectIntegration._id,
environment: envMapping[integrationEnvironment],
appName: integrationApp,
});
if (result?.status == 200) {
let currentSecrets = await getSecretsForProject({
env: integrationEnvironment,
setFileState,
setIsKeyAvailable,
setData,
workspaceId: router.query.id,
});
return <div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
<div className="flex flex-row">
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
ENVIRONMENT
</div>
<ListBox
data={!projectIntegration.isActive && ["Development", "Staging", "Production"]}
selected={integrationEnvironment}
onChange={setIntegrationEnvironment}
/>
</div>
<FontAwesomeIcon
icon={faArrowRight}
className="mx-4 text-gray-400 mt-8"
/>
<div className="mr-2">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
INTEGRATION
</div>
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
{projectIntegration.integration.charAt(0).toUpperCase() + projectIntegration.integration.slice(1)}
</div>
</div>
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
HEROKU APP
</div>
<ListBox
data={!projectIntegration.isActive && apps}
selected={integrationApp}
onChange={setIntegrationApp}
/>
</div>
</div>
<div className="flex flex-row mt-6">
{projectIntegration.isActive
? <div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
<FontAwesomeIcon icon={faRotate} className="text-lg mr-2.5 text-primary animate-spin"/>
<div className="text-gray-300 font-semibold">In Sync</div>
</div>
: <Button
text="Start Integration"
onButtonPressed={async () => {
const result = await startIntegration({
"integrationId": projectIntegration._id,
"environment": envMapping[integrationEnvironment],
"appName": integrationApp
})
if (result?.status == 200) {
let currentSecrets = await getSecretsForProject({
env: integrationEnvironment,
setFileState,
setIsKeyAvailable,
setData,
workspaceId: router.query.id
})
let obj = Object.assign(
{},
...currentSecrets.map((row) => ({ [row[2]]: row[3] }))
);
await pushKeysIntegration({obj, integrationId: projectIntegration._id});
router.reload();
}
}}
color="mineshaft"
size="md"
/>
}
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
<Button
onButtonPressed={async () => {
await deleteIntegration({integrationId: projectIntegration._id});
router.reload();
}}
color="red"
size="icon-md"
icon={faX}
/>
</div>
</div>
</div>
</div>;
}
let obj = Object.assign(
{},
...currentSecrets.map((row) => ({
[row[2]]: row[3],
}))
);
await pushKeysIntegration({
obj,
integrationId: projectIntegration._id,
});
router.reload();
}
}}
color="mineshaft"
size="md"
/>
)}
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
<Button
onButtonPressed={async () => {
await deleteIntegration({
integrationId: projectIntegration._id,
});
router.reload();
}}
color="red"
size="icon-md"
icon={faX}
/>
</div>
</div>
</div>
</div>
);
};
export default function Integrations() {
const [integrations, setIntegrations] = useState();
const [projectIntegrations, setProjectIntegrations] = useState();
const [authorizations, setAuthorizations] = useState();
const router = useRouter();
const [csrfToken, setCsrfToken] = useState("");
const [integrations, setIntegrations] = useState();
const [projectIntegrations, setProjectIntegrations] = useState();
const [authorizations, setAuthorizations] = useState();
const router = useRouter();
const [csrfToken, setCsrfToken] = useState("");
useEffect(async () => {
const tempCSRFToken = crypto.randomBytes(16).toString("hex");
setCsrfToken(tempCSRFToken);
localStorage.setItem("latestCSRFToken", tempCSRFToken);
useEffect(async () => {
const tempCSRFToken = crypto.randomBytes(16).toString("hex");
setCsrfToken(tempCSRFToken);
localStorage.setItem("latestCSRFToken", tempCSRFToken);
let projectAuthorizations = await getWorkspaceAuthorizations({workspaceId: router.query.id});
setAuthorizations(projectAuthorizations);
let projectAuthorizations = await getWorkspaceAuthorizations({
workspaceId: router.query.id,
});
setAuthorizations(projectAuthorizations);
const projectIntegrations = await getWorkspaceIntegrations({workspaceId: router.query.id});
setProjectIntegrations(projectIntegrations);
try {
const integrationsData = await getIntegrations();
setIntegrations(integrationsData);
} catch (error) {
console.log("Error", error);
}
}, []);
const projectIntegrations = await getWorkspaceIntegrations({
workspaceId: router.query.id,
});
setProjectIntegrations(projectIntegrations);
return integrations ? (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Dashboard</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta
property="og:title"
content="Manage your .env files in seconds"
/>
<meta
name="og:description"
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
/>
</Head>
<div className="flex flex-row">
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
<NavHeader pageName="Project Integrations" isProjectRelated={true}/>
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">
Current Project Integrations
</p>
</div>
<p className="mr-4 text-base text-gray-400">
Manage your integrations of Infisical with third-party services.
</p>
</div>
{projectIntegrations.length > 0
? projectIntegrations.map(projectIntegration =>
<Integration key={guidGenerator()} projectIntegration={projectIntegration} />
)
: <div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-col text-gray-400 items-center justify-center">
<div className="mb-1">
You don't have any integrations set up yet. When you do, they will appear here.
</div>
<div className="">
To start, click on any of the options below. It takes 5 clicks to set up.
</div>
</div>
</div>
}
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">
Available Integrations
</p>
</div>
<p className="mr-4 text-base text-gray-400">
Click on the itegration you want to connect. This will let your environment variables flow automatically into selected third-party services.
</p>
<p className="mr-4 text-xs text-gray-600 mt-1">
Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. The core Infisical service will always stay end-to-end encrypted. With any questions, reach out support@infisical.com.
</p>
</div>
<div className="grid gap-4 grid-cols-3 grid-rows-3 mx-6 mt-4 max-w-5xl">
{Object.keys(integrations).map(integration =>
<div className={`relative ${["Heroku"].includes(integrations[integration].name) ? "" : "opacity-50"}`} key={integrations[integration].name} >
<a
href={`${["Heroku"].includes(integrations[integration].name) ? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}` : "#"}`}
rel="noopener"
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${["Heroku"].includes(integrations[integration].name) ? "hover:bg-white/10 duration-200 cursor-pointer" : "cursor-default grayscale"}`}>
<Image
src={`/images/integrations/${integrations[integration].name}.png`}
height={100}
width={100}
alt="integration logo"
></Image>
{integrations[integration].name.split(" ").length > 2
? <div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-8 max-w-xs">
<div>{integrations[integration].name.split(" ")[0]}</div>
<div className="text-base">{integrations[integration].name.split(" ")[1]}{" "}{integrations[integration].name.split(" ")[2]}</div>
</div>
: <div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">{integrations[integration].name}</div>
}
</a>
{["Heroku"].includes(integrations[integration].name) && authorizations.map(authorization => authorization.integration).includes(integrations[integration].name.toLowerCase()) &&
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div onClick={() => {
deleteIntegrationAuth({integrationAuthId: authorizations.filter(authorization => authorization.integration == integrations[integration].name.toLowerCase()).map(authorization => authorization._id)[0]});
router.reload();
}} className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"><FontAwesomeIcon icon={faX} className="text-xs mr-2 py-px"/>Revoke</div>
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200"><FontAwesomeIcon icon={faCheck} className="text-xs mr-2"/>Authorized</div>
</div>
}
{!["Heroku"].includes(integrations[integration].name) &&
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">Coming Soon</div>
</div>
}
</div>
)}
</div>
</div>
</div>
</div>
) : (
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
<Image
src="/images/loading/loading.gif"
height={70}
width={120}
alt="loading animation"
></Image>
</div>
);
try {
const integrationsData = await getIntegrations();
setIntegrations(integrationsData);
} catch (error) {
console.log("Error", error);
}
}, []);
return integrations ? (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Dashboard</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta property="og:title" content="Manage your .env files in seconds" />
<meta
name="og:description"
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
/>
</Head>
<div className="flex flex-row">
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
<NavHeader pageName="Project Integrations" isProjectRelated={true} />
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">Current Project Integrations</p>
</div>
<p className="mr-4 text-base text-gray-400">
Manage your integrations of Infisical with third-party services.
</p>
</div>
{projectIntegrations.length > 0 ? (
projectIntegrations.map((projectIntegration) => (
<Integration
key={guidGenerator()}
projectIntegration={projectIntegration}
/>
))
) : (
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-col text-gray-400 items-center justify-center">
<div className="mb-1">
You {"don't"} have any integrations set up yet. When you do,
they will appear here.
</div>
<div className="">
To start, click on any of the options below. It takes 5 clicks
to set up.
</div>
</div>
</div>
)}
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">Available Integrations</p>
</div>
<p className="mr-4 text-base text-gray-400">
Click on the itegration you want to connect. This will let your
environment variables flow automatically into selected third-party
services.
</p>
<p className="mr-4 text-xs text-gray-600 mt-1">
Note: during an integration with Heroku, for security reasons, it
is impossible to maintain end-to-end encryption. In theory, this
lets Infisical decrypt yor environment variables. In practice, we
can assure you that this will never be done, and it allows us to
protect your secrets from bad actors online. The core Infisical
service will always stay end-to-end encrypted. With any questions,
reach out support@infisical.com.
</p>
</div>
<div className="grid gap-4 grid-cols-3 grid-rows-3 mx-6 mt-4 max-w-5xl">
{Object.keys(integrations).map((integration) => (
<div
className={`relative ${
["Heroku"].includes(integrations[integration].name)
? ""
: "opacity-50"
}`}
key={integrations[integration].name}
>
<a
href={`${
["Heroku"].includes(integrations[integration].name)
? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}`
: "#"
}`}
rel="noopener"
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${
["Heroku"].includes(integrations[integration].name)
? "hover:bg-white/10 duration-200 cursor-pointer"
: "cursor-default grayscale"
}`}
>
<Image
src={`/images/integrations/${integrations[integration].name}.png`}
height={100}
width={100}
alt="integration logo"
></Image>
{integrations[integration].name.split(" ").length > 2 ? (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-8 max-w-xs">
<div>{integrations[integration].name.split(" ")[0]}</div>
<div className="text-base">
{integrations[integration].name.split(" ")[1]}{" "}
{integrations[integration].name.split(" ")[2]}
</div>
</div>
) : (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">
{integrations[integration].name}
</div>
)}
</a>
{["Heroku"].includes(integrations[integration].name) &&
authorizations
.map((authorization) => authorization.integration)
.includes(integrations[integration].name.toLowerCase()) && (
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div
onClick={() => {
deleteIntegrationAuth({
integrationAuthId: authorizations
.filter(
(authorization) =>
authorization.integration ==
integrations[integration].name.toLowerCase()
)
.map((authorization) => authorization._id)[0],
});
router.reload();
}}
className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"
>
<FontAwesomeIcon
icon={faX}
className="text-xs mr-2 py-px"
/>
Revoke
</div>
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200">
<FontAwesomeIcon
icon={faCheck}
className="text-xs mr-2"
/>
Authorized
</div>
</div>
)}
{!["Heroku"].includes(integrations[integration].name) && (
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">
Coming Soon
</div>
</div>
)}
</div>
))}
</div>
</div>
</div>
</div>
) : (
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
<Image
src="/images/loading/loading.gif"
height={70}
width={120}
alt="loading animation"
></Image>
</div>
);
}
Integrations.requireAuth = true;
+126 -134
View File
@@ -1,147 +1,139 @@
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import InputField from "../components/basic/InputField";
import Error from "../components/basic/Error";
import Button from "../components/basic/buttons/Button";
import getWorkspaces from "./api/workspace/getWorkspaces";
import attemptLogin from "../components/utilities/attemptLogin";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { useRouter } from "next/router";
import useTranslation from "next-translate/useTranslation";
import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button";
import Error from "~/components/basic/Error";
import InputField from "~/components/basic/InputField";
import attemptLogin from "~/utilities/attemptLogin";
import getWorkspaces from "./api/workspace/getWorkspaces";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const { t } = useTranslation("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const { t } = useTranslation("");
useEffect(async () => {
let userWorkspace;
try {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id;
router.push("/dashboard/" + userWorkspace);
} catch (error) {
console.log("Error - Not logged in yet");
}
}, []);
useEffect(async () => {
let userWorkspace;
try {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id;
router.push("/dashboard/" + userWorkspace);
} catch (error) {
console.log("Error - Not logged in yet");
}
}, []);
/**
* This function check if the user entered the correct credentials and should be allowed to log in.
*/
const loginCheck = async () => {
setIsLoading(true);
await attemptLogin(
email,
password,
setErrorLogin,
router,
false,
true
).then(() => {
setTimeout(function () {
setIsLoading(false);
}, 2000);
});
};
/**
* This function check if the user entered the correct credentials and should be allowed to log in.
*/
const loginCheck = async () => {
setIsLoading(true);
await attemptLogin(
email,
password,
setErrorLogin,
router,
false,
true
).then(() => {
setTimeout(function () {
setIsLoading(false);
}, 2000);
});
};
return (
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
<Head>
<title>{t("auth:meta.login.title")}</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta
property="og:title"
content={t("auth:meta.login.og-title")}
/>
<meta
name="og:description"
content={t("auth:meta.login.og-description")}
/>
</Head>
<Link href="/">
<div className="flex justify-center mb-8 mt-20 cursor-pointer">
<Image
src="/images/biglogo.png"
height={90}
width={120}
alt="long logo"
/>
</div>
</Link>
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
<p className="text-4xl flex justify-center font-semibold text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
{t("auth:login")}
</p>
<div className="flex flex-row items-center justify-center">
<p className="text-md flex justify-center mt-2 text-gray-400">
{t("auth:need-account")}
</p>
</div>
<div className="flex flex-col items-center justify-center w-full md:pb-4 max-h-24 max-w-md mx-auto">
<Link href="/signup">
<button className="w-full pb-3 hover:opacity-90 duration-200">
<u className="font-normal text-md text-sky-500">
{t("auth:create-account")}
</u>
</button>
</Link>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
<InputField
label={t("common:email")}
onChangeHandler={setEmail}
type="email"
value={email}
placeholder=""
isRequired
/>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
<InputField
label={t("common:password")}
onChangeHandler={setPassword}
type="password"
value={password}
placeholder=""
isRequired
/>
</div>
{errorLogin && (
<Error text="Your email and/or password are wrong." />
)}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button
text={t("auth: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">
return (
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
<Head>
<title>{t("auth:meta.login.title")}</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta property="og:title" content={t("auth:meta.login.og-title")} />
<meta
name="og:description"
content={t("auth:meta.login.og-description")}
/>
</Head>
<Link href="/">
<div className="flex justify-center mb-8 mt-20 cursor-pointer">
<Image
src="/images/biglogo.png"
height={90}
width={120}
alt="long logo"
/>
</div>
</Link>
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
<p className="text-4xl flex justify-center font-semibold text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
{t("auth:login")}
</p>
<div className="flex flex-row items-center justify-center">
<p className="text-md flex justify-center mt-2 text-gray-400">
{t("auth:need-account")}
</p>
</div>
<div className="flex flex-col items-center justify-center w-full md:pb-4 max-h-24 max-w-md mx-auto">
<Link href="/signup">
<button className="w-full pb-3 hover:opacity-90 duration-200">
<u className="font-normal text-md text-sky-500">
{t("auth:create-account")}
</u>
</button>
</Link>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
<InputField
label={t("common:email")}
onChangeHandler={setEmail}
type="email"
value={email}
placeholder=""
isRequired
/>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
<InputField
label={t("common:password")}
onChangeHandler={setPassword}
type="password"
value={password}
placeholder=""
isRequired
/>
</div>
{errorLogin && <Error text="Your email and/or password are wrong." />}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button
text={t("auth: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"
/>
</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("auth:maintenance-alert")}
</div>
)}
</div>
);
{t("auth:maintenance-alert")}
</div>
)}
</div>
);
}
+17 -13
View File
@@ -1,20 +1,24 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function NoProjects() {
return (
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
<FontAwesomeIcon icon={faFolderOpen} className="text-7xl mb-8 w-full px-auto" />
<div className="max-w-md">
You are not part of any projects in this organization yet. When you do, they will appear here.
</div>
<div className="max-w-md mt-4">
Create a new project, or ask
other organization members to give you neccessary permissions.
</div>
</div>
);
return (
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
<FontAwesomeIcon
icon={faFolderOpen}
className="text-7xl mb-8 w-full px-auto"
/>
<div className="max-w-md">
You are not part of any projects in this organization yet. When you do,
they will appear here.
</div>
<div className="max-w-md mt-4">
Create a new project, or ask other organization members to give you
neccessary permissions.
</div>
</div>
);
}
NoProjects.requireAuth = true;
+17 -17
View File
@@ -2,21 +2,21 @@ import React from "react";
import Head from "next/head";
export default function Activity() {
return (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
<Head>
<title>Request a New Invite</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-col items-center justify-center text-gray-200 h-screen w-screen">
<p className="text-6xl">Oops.</p>
<p className="mt-2 mb-1 text-xl">Your invite has expired. </p>
<p className="text-xl">Ask the administrator for a new one.</p>
<p className="text-md mt-8 text-gray-600 max-w-sm text-center">
Note: If it still doesn't work, please reach out to us at
support@infisical.com
</p>
</div>
</div>
);
return (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
<Head>
<title>Request a New Invite</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-col items-center justify-center text-gray-200 h-screen w-screen">
<p className="text-6xl">Oops.</p>
<p className="mt-2 mb-1 text-xl">Your invite has expired. </p>
<p className="text-xl">Ask the administrator for a new one.</p>
<p className="text-md mt-8 text-gray-600 max-w-sm text-center">
Note: If it still {"doesn't work"}, please reach out to us at
support@infisical.com
</p>
</div>
</div>
);
}
+96 -90
View File
@@ -1,103 +1,109 @@
import React, { useState, useEffect } from "react";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import Plan from "../../../components/billing/Plan";
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription"
import getOrganizationUsers from "../../api/organization/GetOrgUsers"
import NavHeader from "../../../components/navigation/NavHeader";
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from "../../../components/utilities/config";
import Plan from "~/components/billing/Plan";
import NavHeader from "~/components/navigation/NavHeader";
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from "~/utilities/config";
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription";
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
export default function SettingsBilling() {
let [currentPlan, setCurrentPlan] = useState("");
let [numUsers, setNumUsers] = useState("");
let [currentPlan, setCurrentPlan] = useState("");
let [numUsers, setNumUsers] = useState("");
const plans = [
{
key: 1,
name: "Starter",
price: "Free",
priceExplanation: "up to 5 team members",
text: "Manage any project with 5 members for free!",
subtext: "$5 per member/month afterwards.",
buttonTextMain: "Downgrade",
buttonTextSecondary: "Learn More",
current: currentPlan == STRIPE_PRODUCT_STARTER,
},
{
key: 2,
name: "Professional",
price: "$9",
priceExplanation: "/member/month",
subtext: "Includes unlimited projects & members.",
text: "Keep up with key management as you grow.",
buttonTextMain: "Upgrade",
buttonTextSecondary: "Learn More",
current: currentPlan == STRIPE_PRODUCT_PRO,
},
{
key: 3,
name: "Enterprise",
price: "Custom Pricing",
text: "More control for advanced organizations.",
buttonTextMain: "Schedule a Demo",
buttonTextSecondary: "Learn More",
current: false,
},
];
const plans = [
{
key: 1,
name: "Starter",
price: "Free",
priceExplanation: "up to 5 team members",
text: "Manage any project with 5 members for free!",
subtext: "$5 per member/month afterwards.",
buttonTextMain: "Downgrade",
buttonTextSecondary: "Learn More",
current: currentPlan == STRIPE_PRODUCT_STARTER,
},
{
key: 2,
name: "Professional",
price: "$9",
priceExplanation: "/member/month",
subtext: "Includes unlimited projects & members.",
text: "Keep up with key management as you grow.",
buttonTextMain: "Upgrade",
buttonTextSecondary: "Learn More",
current: currentPlan == STRIPE_PRODUCT_PRO,
},
{
key: 3,
name: "Enterprise",
price: "Custom Pricing",
text: "More control for advanced organizations.",
buttonTextMain: "Schedule a Demo",
buttonTextSecondary: "Learn More",
current: false,
},
];
useEffect(async () => {
const subscriptions = await getOrganizationSubscriptions({orgId: localStorage.getItem("orgData.id")});
setCurrentPlan(subscriptions.data[0].plan.product)
const orgUsers = await getOrganizationUsers({orgId: localStorage.getItem("orgData.id")})
setNumUsers(orgUsers.length)
}, []);
useEffect(async () => {
const subscriptions = await getOrganizationSubscriptions({
orgId: localStorage.getItem("orgData.id"),
});
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Settings - Billing</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Usage & Billing"/>
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
Usage & Billing
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your organization's subscription here.
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<p className="text-xl font-semibold">Subscription</p>
<div className="flex flex-row mt-4 overflow-x-auto">
{plans.map((plan) => (
<Plan
key={plan.name}
plan={plan}
/>
))}
</div>
<p className='text-xl font-bold mt-12'>Current Usage</p>
<div className="flex flex-row">
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
<p className="text-6xl font-bold">{numUsers}</p>
<p className="text-gray-300">{numUsers > 1 ? "Organization members" : "Organization member"}</p>
</div>
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
setCurrentPlan(subscriptions.data[0].plan.product);
const orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
setNumUsers(orgUsers.length);
}, []);
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Settings - Billing</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Usage & Billing" />
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
Usage & Billing
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your {"organization's"} subscription here.
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<p className="text-xl font-semibold">Subscription</p>
<div className="flex flex-row mt-4 overflow-x-auto">
{plans.map((plan) => (
<Plan key={plan.name} plan={plan} />
))}
</div>
<p className="text-xl font-bold mt-12">Current Usage</p>
<div className="flex flex-row">
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
<p className="text-6xl font-bold">{numUsers}</p>
<p className="text-gray-300">
{numUsers > 1
? "Organization members"
: "Organization member"}
</p>
</div>
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
<p className="text-6xl font-bold">1 </p>
<p className="text-gray-300">Organization projects</p>
</div> */}
</div>
</div>
</div>
</div>
</div>
);
</div>
</div>
</div>
</div>
</div>
);
}
SettingsBilling.requireAuth = true;
+318 -346
View File
@@ -1,372 +1,344 @@
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/router";
import useTranslation from "next-translate/useTranslation";
import {
faMagnifyingGlass,
faPlus,
faX,
faMagnifyingGlass,
faPlus,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import InputField from "../../../components/basic/InputField";
import getWorkspaces from "../../api/workspace/getWorkspaces";
import AddIncidentContactDialog from "../../../components/basic/dialog/AddIncidentContactDialog";
import getIncidentContacts from "../../api/organization/getIncidentContacts";
import deleteIncidentContact from "../../api/organization/deleteIncidentContact";
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import AddUserDialog from "../../../components/basic/dialog/AddUserDialog";
import UserTable from "../../../components/basic/table/UserTable";
import getUser from "../../api/user/getUser";
import guidGenerator from "../../../components/utilities/randomId";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button";
import AddIncidentContactDialog from "~/components/basic/dialog/AddIncidentContactDialog";
import AddUserDialog from "~/components/basic/dialog/AddUserDialog";
import InputField from "~/components/basic/InputField";
import UserTable from "~/components/basic/table/UserTable";
import NavHeader from "~/components/navigation/NavHeader";
import guidGenerator from "~/utilities/randomId";
import addUserToOrg from "../../api/organization/addUserToOrg";
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
import renameOrg from "../../api/organization/renameOrg";
import deleteIncidentContact from "../../api/organization/deleteIncidentContact";
import getIncidentContacts from "../../api/organization/getIncidentContacts";
import getOrganization from "../../api/organization/GetOrg";
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription";
import NavHeader from "../../../components/navigation/NavHeader";
import Button from "../../../components/basic/buttons/Button";
import useTranslation from "next-translate/useTranslation";
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
import renameOrg from "../../api/organization/renameOrg";
import getUser from "../../api/user/getUser";
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import getWorkspaces from "../../api/workspace/getWorkspaces";
export default function SettingsOrg() {
const [buttonReady, setButtonReady] = useState(false);
const router = useRouter();
const [orgName, setOrgName] = useState("");
const [emailUser, setEmailUser] = useState("");
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] =
useState("");
const [searchUsers, setSearchUsers] = useState("");
const [workspaceId, setWorkspaceId] = useState("");
const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] =
useState(false);
const [isAddUserOpen, setIsAddUserOpen] = useState(
router.asPath.split("?")[1] == "invite"
);
const [incidentContacts, setIncidentContacts] = useState([]);
const [searchIncidentContact, setSearchIncidentContact] = useState("");
const [userList, setUserList] = useState();
const [personalEmail, setPersonalEmail] = useState("");
let workspaceIdTemp;
const [email, setEmail] = useState("");
const [currentPlan, setCurrentPlan] = useState("");
const [buttonReady, setButtonReady] = useState(false);
const router = useRouter();
const [orgName, setOrgName] = useState("");
const [emailUser, setEmailUser] = useState("");
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState("");
const [searchUsers, setSearchUsers] = useState("");
const [workspaceId, setWorkspaceId] = useState("");
const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] =
useState(false);
const [isAddUserOpen, setIsAddUserOpen] = useState(
router.asPath.split("?")[1] == "invite"
);
const [incidentContacts, setIncidentContacts] = useState([]);
const [searchIncidentContact, setSearchIncidentContact] = useState("");
const [userList, setUserList] = useState();
const [personalEmail, setPersonalEmail] = useState("");
let workspaceIdTemp;
const [email, setEmail] = useState("");
const [currentPlan, setCurrentPlan] = useState("");
const { t } = useTranslation();
const { t } = useTranslation();
useEffect(async () => {
let org = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
});
let orgData = org;
setOrgName(orgData.name);
let incidentContactsData = await getIncidentContacts(
localStorage.getItem("orgData.id")
);
useEffect(async () => {
let org = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
});
let orgData = org;
setOrgName(orgData.name);
let incidentContactsData = await getIncidentContacts(
localStorage.getItem("orgData.id")
);
setIncidentContacts(
incidentContactsData?.map((contact) => contact.email)
);
setIncidentContacts(incidentContactsData?.map((contact) => contact.email));
const user = await getUser();
setPersonalEmail(user.email);
const user = await getUser();
setPersonalEmail(user.email);
workspaceIdTemp = router.query.id;
let orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
setUserList(
orgUsers.map((user) => ({
key: guidGenerator(),
firstName: user.user?.firstName,
lastName: user.user?.lastName,
email:
user.user?.email == null
? user.inviteEmail
: user.user?.email,
role: user?.role,
status: user?.status,
userId: user.user?._id,
membershipId: user._id,
publicKey: user.user?.publicKey,
}))
);
const subscriptions = await getOrganizationSubscriptions({
orgId: localStorage.getItem("orgData.id"),
});
setCurrentPlan(subscriptions.data[0].plan.product);
}, []);
workspaceIdTemp = router.query.id;
let orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
setUserList(
orgUsers.map((user) => ({
key: guidGenerator(),
firstName: user.user?.firstName,
lastName: user.user?.lastName,
email: user.user?.email == null ? user.inviteEmail : user.user?.email,
role: user?.role,
status: user?.status,
userId: user.user?._id,
membershipId: user._id,
publicKey: user.user?.publicKey,
}))
);
const subscriptions = await getOrganizationSubscriptions({
orgId: localStorage.getItem("orgData.id"),
});
setCurrentPlan(subscriptions.data[0].plan.product);
}, []);
const modifyOrgName = (newName) => {
setButtonReady(true);
setOrgName(newName);
};
const modifyOrgName = (newName) => {
setButtonReady(true);
setOrgName(newName);
};
const submitChanges = (newOrgName) => {
renameOrg(localStorage.getItem("orgData.id"), newOrgName);
setButtonReady(false);
};
const submitChanges = (newOrgName) => {
renameOrg(localStorage.getItem("orgData.id"), newOrgName);
setButtonReady(false);
};
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
function closeAddUserModal() {
setIsAddUserOpen(false);
}
function closeAddUserModal() {
setIsAddUserOpen(false);
}
function closeAddIncidentContactModal() {
setIsAddIncidentContactOpen(false);
}
function closeAddIncidentContactModal() {
setIsAddIncidentContactOpen(false);
}
function openAddUserModal() {
setIsAddUserOpen(true);
}
function openAddUserModal() {
setIsAddUserOpen(true);
}
function openAddIncidentContactModal() {
setIsAddIncidentContactOpen(true);
}
function openAddIncidentContactModal() {
setIsAddIncidentContactOpen(true);
}
async function submitAddUserModal(email) {
await addUserToOrg(email, localStorage.getItem("orgData.id"));
setEmail("");
setIsAddUserOpen(false);
router.reload();
}
async function submitAddUserModal(email) {
await addUserToOrg(email, localStorage.getItem("orgData.id"));
setEmail("");
setIsAddUserOpen(false);
router.reload();
}
const deleteIncidentContactFully = (incidentContact) => {
setIncidentContacts(
incidentContacts.filter((contact) => contact != incidentContact)
);
deleteIncidentContact(
localStorage.getItem("orgData.id"),
incidentContact
);
};
const deleteIncidentContactFully = (incidentContact) => {
setIncidentContacts(
incidentContacts.filter((contact) => contact != incidentContact)
);
deleteIncidentContact(localStorage.getItem("orgData.id"), incidentContact);
};
/**
* This function deleted a workspace.
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
* It then deletes the workspace and forwards the user to another aviable workspace.
*/
const executeDeletingWorkspace = async () => {
let userWorkspaces = await getWorkspaces();
/**
* This function deleted a workspace.
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
* It then deletes the workspace and forwards the user to another aviable workspace.
*/
const executeDeletingWorkspace = async () => {
let userWorkspaces = await getWorkspaces();
if (userWorkspaces.length > 1) {
if (
userWorkspaces.filter(
(workspace) => workspace._id == router.query.id
)[0].name == workspaceToBeDeletedName
) {
await deleteWorkspace(router.query.id);
let userWorkspaces = await getWorkspaces();
router.push("/dashboard/" + userWorkspaces[0]._id);
}
}
};
if (userWorkspaces.length > 1) {
if (
userWorkspaces.filter(
(workspace) => workspace._id == router.query.id
)[0].name == workspaceToBeDeletedName
) {
await deleteWorkspace(router.query.id);
let userWorkspaces = await getWorkspaces();
router.push("/dashboard/" + userWorkspaces[0]._id);
}
}
};
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>{t("settings:meta.org.head-title")}</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName={t("settings:meta.org.title")} />
<AddIncidentContactDialog
isOpen={isAddIncidentContactOpen}
closeModal={closeAddIncidentContactModal}
workspaceId={workspaceId}
incidentContacts={incidentContacts}
setIncidentContacts={setIncidentContacts}
/>
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
{t("settings:meta.org.title")}
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
{t("settings:meta.org.description")}
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
<div className="flex flex-col">
<div className="min-w-md mt-2 flex flex-col items-end pb-4">
<div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
<div className="max-h-28 w-full max-w-md mr-auto">
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
{t("common:display-name")}
</p>
<InputField
// label="Organization Name"
onChangeHandler={modifyOrgName}
type="varName"
value={orgName}
placeholder=""
isRequired
/>
</div>
<div className="flex justify-start w-full">
<div
className={`flex justify-start max-w-sm mt-4 mb-2`}
>
<Button
text={t("common:save-changes")}
onButtonPressed={() =>
submitChanges(orgName)
}
color="mineshaft"
size="md"
active={buttonReady}
iconDisabled={faCheck}
textDisabled={t("common:saved")}
/>
</div>
</div>
</div>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6">
<p className="font-semibold mr-4 text-white text-xl">
{t("settings:org-members")}
</p>
<p className="mr-4 text-gray-400 mt-2 mb-2">
{t("settings:org-members-description")}
</p>
<AddUserDialog
isOpen={isAddUserOpen}
closeModal={closeAddUserModal}
submitModal={submitAddUserModal}
email={emailUser}
workspaceId={workspaceId}
setEmail={setEmailUser}
currentPlan={currentPlan}
orgName={orgName}
/>
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
<div className="pb-1 w-full flex flex-row items-start max-w-6xl">
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center">
<FontAwesomeIcon
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
icon={faMagnifyingGlass}
/>
<input
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchUsers}
onChange={(e) =>
setSearchUsers(e.target.value)
}
placeholder={t(
"settings:search-members"
)}
/>
</div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
<Button
text={t("settings:add-member")}
onButtonPressed={openAddUserModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
</div>
</div>
{userList && (
<div className="overflow-y-auto max-w-6xl w-full">
<UserTable
userData={userList}
changeData={setUserList}
myUser={personalEmail}
filter={searchUsers.toLowerCase()}
resendInvite={submitAddUserModal}
isOrg={true}
// onClick={openDeleteModal}
// deleteUser={deleteMembership}
// setUserIdToBeDeleted={setUserIdToBeDeleted}
className="w-full"
/>
</div>
)}
</div>
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>{t("settings:meta.org.head-title")}</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName={t("settings:meta.org.title")} />
<AddIncidentContactDialog
isOpen={isAddIncidentContactOpen}
closeModal={closeAddIncidentContactModal}
workspaceId={workspaceId}
incidentContacts={incidentContacts}
setIncidentContacts={setIncidentContacts}
/>
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
{t("settings:meta.org.title")}
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
{t("settings:meta.org.description")}
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
<div className="flex flex-col">
<div className="min-w-md mt-2 flex flex-col items-end pb-4">
<div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
<div className="max-h-28 w-full max-w-md mr-auto">
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
{t("common:display-name")}
</p>
<InputField
// label="Organization Name"
onChangeHandler={modifyOrgName}
type="varName"
value={orgName}
placeholder=""
isRequired
/>
</div>
<div className="flex justify-start w-full">
<div className={`flex justify-start max-w-sm mt-4 mb-2`}>
<Button
text={t("common:save-changes")}
onButtonPressed={() => submitChanges(orgName)}
color="mineshaft"
size="md"
active={buttonReady}
iconDisabled={faCheck}
textDisabled={t("common:saved")}
/>
</div>
</div>
</div>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6">
<p className="font-semibold mr-4 text-white text-xl">
{t("settings:org-members")}
</p>
<p className="mr-4 text-gray-400 mt-2 mb-2">
{t("settings:org-members-description")}
</p>
<AddUserDialog
isOpen={isAddUserOpen}
closeModal={closeAddUserModal}
submitModal={submitAddUserModal}
email={emailUser}
workspaceId={workspaceId}
setEmail={setEmailUser}
currentPlan={currentPlan}
orgName={orgName}
/>
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
<div className="pb-1 w-full flex flex-row items-start max-w-6xl">
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center">
<FontAwesomeIcon
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
icon={faMagnifyingGlass}
/>
<input
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchUsers}
onChange={(e) => setSearchUsers(e.target.value)}
placeholder={t("settings:search-members")}
/>
</div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
<Button
text={t("settings:add-member")}
onButtonPressed={openAddUserModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
</div>
</div>
{userList && (
<div className="overflow-y-auto max-w-6xl w-full">
<UserTable
userData={userList}
changeData={setUserList}
myUser={personalEmail}
filter={searchUsers.toLowerCase()}
resendInvite={submitAddUserModal}
isOrg={true}
// onClick={openDeleteModal}
// deleteUser={deleteMembership}
// setUserIdToBeDeleted={setUserIdToBeDeleted}
className="w-full"
/>
</div>
)}
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
<div className="flex flex-col justify-between w-full max-w-3xl">
<p className="text-xl font-semibold mb-3 min-w-max">
{t("settings:incident-contacts")}
</p>
<p className="text-xs text-gray-500 mb-2 min-w-max">
{t(
"settings:incident-contacts-description"
)}
</p>
</div>
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
<Button
text={t("common:add-contact")}
onButtonPressed={
openAddIncidentContactModal
}
color="mineshaft"
size="md"
icon={faPlus}
/>
</div>
</div>
<div className="h-12 w-full max-w-5xl bg-white/5 mt-2 flex items-center rounded-t-md flex flwex-row items-center">
<FontAwesomeIcon
className="bg-white/5 rounded-tl-md py-4 pl-4 pr-2 text-gray-400"
icon={faMagnifyingGlass}
/>
<input
className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none"
value={searchIncidentContact}
onChange={(e) =>
setSearchIncidentContact(e.target.value)
}
placeholder={t("settings:search")}
/>
</div>
{incidentContacts?.filter((email) =>
email.includes(searchIncidentContact)
).length > 0 ? (
incidentContacts
.filter((email) =>
email.includes(searchIncidentContact)
)
.map((contact) => (
<div
key={guidGenerator()}
className="flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full"
>
<p className="text-gray-300">
{contact}
</p>
<div className="opacity-50 hover:opacity-100 duration-200">
<Button
onButtonPressed={() =>
deleteIncidentContactFully(
contact
)
}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
</div>
))
) : (
<div className="w-full flex flex-row justify-center mt-6 max-w-4xl ml-6">
<p className="text-gray-400">
{t("settings:no-incident-contacts")}
</p>
</div>
)}
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
<div className="flex flex-col justify-between w-full max-w-3xl">
<p className="text-xl font-semibold mb-3 min-w-max">
{t("settings:incident-contacts")}
</p>
<p className="text-xs text-gray-500 mb-2 min-w-max">
{t("settings:incident-contacts-description")}
</p>
</div>
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
<Button
text={t("common:add-contact")}
onButtonPressed={openAddIncidentContactModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
</div>
</div>
<div className="h-12 w-full max-w-5xl bg-white/5 mt-2 flex items-center rounded-t-md flex flwex-row items-center">
<FontAwesomeIcon
className="bg-white/5 rounded-tl-md py-4 pl-4 pr-2 text-gray-400"
icon={faMagnifyingGlass}
/>
<input
className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none"
value={searchIncidentContact}
onChange={(e) => setSearchIncidentContact(e.target.value)}
placeholder={t("settings:search")}
/>
</div>
{incidentContacts?.filter((email) =>
email.includes(searchIncidentContact)
).length > 0 ? (
incidentContacts
.filter((email) => email.includes(searchIncidentContact))
.map((contact) => (
<div
key={guidGenerator()}
className="flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full"
>
<p className="text-gray-300">{contact}</p>
<div className="opacity-50 hover:opacity-100 duration-200">
<Button
onButtonPressed={() =>
deleteIncidentContactFully(contact)
}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
</div>
))
) : (
<div className="w-full flex flex-row justify-center mt-6 max-w-4xl ml-6">
<p className="text-gray-400">
{t("settings:no-incident-contacts")}
</p>
</div>
)}
</div>
{/* <div className="border-l border-red pb-4 pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pt-2 max-w-6xl">
{/* <div className="border-l border-red pb-4 pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pt-2 max-w-6xl">
<p className="text-xl font-bold text-red">
Danger Zone
</p>
@@ -401,11 +373,11 @@ export default function SettingsOrg() {
have more than one.
</p>
</div> */}
</div>
</div>
</div>
</div>
);
</div>
</div>
</div>
</div>
);
}
SettingsOrg.requireAuth = true;
+266 -187
View File
@@ -1,61 +1,60 @@
import React, { useState, useEffect } from "react";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
import InputField from "../../../components/basic/InputField";
import getUser from "../../api/user/getUser";
import NavHeader from "../../../components/navigation/NavHeader";
import passwordCheck from "../../../components/utilities/checks/PasswordCheck";
import changePassword from "../../../components/utilities/changePassword";
import issueBackupKey from "../../../components/utilities/issueBackupKey";
import Button from "../../../components/basic/buttons/Button";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button";
import InputField from "~/components/basic/InputField";
import NavHeader from "~/components/navigation/NavHeader";
import changePassword from "~/components/utilities/cryptography/changePassword";
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
import passwordCheck from "~/utilities/checks/PasswordCheck";
import getUser from "../../api/user/getUser";
export default function PersonalSettings() {
const [personalEmail, setPersonalEmail] = useState("");
const [personalName, setPersonalName] = useState("");
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const [currentPasswordError, setCurrentPasswordError] = useState(false);
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [backupPassword, setBackupPassword] = useState("");
const [passwordChanged, setPasswordChanged] = useState(false);
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
const [backupKeyError, setBackupKeyError] = useState(false);
const [personalEmail, setPersonalEmail] = useState("");
const [personalName, setPersonalName] = useState("");
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const [currentPasswordError, setCurrentPasswordError] = useState(false);
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [backupPassword, setBackupPassword] = useState("");
const [passwordChanged, setPasswordChanged] = useState(false);
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
const [backupKeyError, setBackupKeyError] = useState(false);
useEffect(async () => {
let user = await getUser();
setPersonalEmail(user.email);
setPersonalName(user.firstName + " " + user.lastName);
}, []);
useEffect(async () => {
let user = await getUser();
setPersonalEmail(user.email);
setPersonalName(user.firstName + " " + user.lastName);
}, []);
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Personal Settings</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Personal Settings" isProjectRelated={false}/>
<div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
Personal Settings
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your personal information here.
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
<div className="flex flex-col">
<div className="min-w-md flex flex-col items-end pb-4">
{/* <div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Personal Settings</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Personal Settings" isProjectRelated={false} />
<div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
Personal Settings
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your personal information here.
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
<div className="flex flex-col">
<div className="min-w-md flex flex-col items-end pb-4">
{/* <div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
<div className="max-h-28 w-full max-w-md mr-auto">
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
Display Name
@@ -99,143 +98,223 @@ export default function PersonalSettings() {
</div>
</div>
</div> */}
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6">
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
<div className="flex flex-col justify-between w-full max-w-3xl">
<p className="text-xl font-semibold mb-3 min-w-max">
Change password
</p>
</div>
</div>
<div className="max-w-xl w-full">
<InputField
label="Current Password"
onChangeHandler={(password) => {
setCurrentPassword(password);
}}
type="password"
value={currentPassword}
isRequired
error={currentPasswordError}
errorText="The current password may be wrong"
/>
<div className="py-2"></div>
<InputField
label="New Password"
onChangeHandler={(password) => {
setNewPassword(password);
passwordCheck(
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
false
);
}}
type="password"
value={newPassword}
isRequired
error={passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber}
/>
</div>
{(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber) ? <div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2">
<div className={`text-gray-400 text-sm mb-1`}>Password should contain at least:</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorLength ? "text-gray-400" : "text-gray-600"} text-sm`}>14 characters</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"} text-sm`}>1 lowercase character</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorNumber ? "text-gray-400" : "text-gray-600"} text-sm`}>1 number</div>
</div>
</div> : <div className="py-2"></div>}
<div className="flex flex-row items-center mt-3 w-52 pr-3">
<Button
text="Change Password"
onButtonPressed={() => {
if (!passwordErrorLength && !passwordErrorLowerCase && !passwordErrorNumber) {
changePassword(
personalEmail,
currentPassword,
newPassword,
setCurrentPasswordError,
setPasswordChanged,
setCurrentPassword,
setNewPassword
)
}
}}
color="mineshaft"
size="md"
active={(newPassword != "") && (currentPassword != "") && !(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber)}
textDisabled="Change Password"
/>
<FontAwesomeIcon icon={faCheck} className={`ml-4 text-primary text-3xl ${passwordChanged ? "opacity-100" : "opacity-0"} duration-300`}/>
</div>
</div>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6">
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
<div className="flex flex-col justify-between w-full max-w-3xl">
<p className="text-xl font-semibold mb-3 min-w-max">
Change password
</p>
</div>
</div>
<div className="max-w-xl w-full">
<InputField
label="Current Password"
onChangeHandler={(password) => {
setCurrentPassword(password);
}}
type="password"
value={currentPassword}
isRequired
error={currentPasswordError}
errorText="The current password may be wrong"
/>
<div className="py-2"></div>
<InputField
label="New Password"
onChangeHandler={(password) => {
setNewPassword(password);
passwordCheck(
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
false
);
}}
type="password"
value={newPassword}
isRequired
error={
passwordErrorLength &&
passwordErrorLowerCase &&
passwordErrorNumber
}
/>
</div>
{passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber ? (
<div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2">
<div className={`text-gray-400 text-sm mb-1`}>
Password should contain at least:
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLength ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
14 characters
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLowerCase
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
1 lowercase character
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
1 number
</div>
</div>
</div>
) : (
<div className="py-2"></div>
)}
<div className="flex flex-row items-center mt-3 w-52 pr-3">
<Button
text="Change Password"
onButtonPressed={() => {
if (
!passwordErrorLength &&
!passwordErrorLowerCase &&
!passwordErrorNumber
) {
changePassword(
personalEmail,
currentPassword,
newPassword,
setCurrentPasswordError,
setPasswordChanged,
setCurrentPassword,
setNewPassword
);
}
}}
color="mineshaft"
size="md"
active={
newPassword != "" &&
currentPassword != "" &&
!(
passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber
)
}
textDisabled="Change Password"
/>
<FontAwesomeIcon
icon={faCheck}
className={`ml-4 text-primary text-3xl ${
passwordChanged ? "opacity-100" : "opacity-0"
} duration-300`}
/>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 mt-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
<div className="flex flex-col justify-between w-full max-w-3xl">
<p className="text-xl font-semibold mb-3 min-w-max">
Emergency Kit
</p>
<p className="text-sm text-mineshaft-300 min-w-max">
Your Emergency Kit contains the information you’ll need to sign in to your Infisical account.
</p>
<p className="text-sm text-mineshaft-300 mb-5 min-w-max">
Only the latest issued Emergency Kit remains valid. To get a new Emergency Kit, verify your password.
</p>
</div>
</div>
<div className="w-full max-w-xl mb-4">
<InputField
label="Current Password"
onChangeHandler={setBackupPassword}
type="password"
value={backupPassword}
isRequired
error={backupKeyError}
errorText="The current password is wrong"
/>
</div>
<div className="flex flex-row items-center mt-3 w-full w-60">
<Button
text="Download Emergency Kit"
onButtonPressed={() => {
issueBackupKey({
email: personalEmail,
password: backupPassword,
personalName,
setBackupKeyError,
setBackupKeyIssued
});
}}
color="mineshaft"
size="md"
active={backupPassword != ""}
textDisabled="Download Emergency Kit"
/>
<FontAwesomeIcon icon={faCheck} className={`ml-4 text-primary text-3xl ${backupKeyIssued ? "opacity-100" : "opacity-0"} duration-300`}/>
</div>
</div>
</div>
</div>
</div>
</div>
);
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 mt-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
<div className="flex flex-col justify-between w-full max-w-3xl">
<p className="text-xl font-semibold mb-3 min-w-max">
Emergency Kit
</p>
<p className="text-sm text-mineshaft-300 min-w-max">
Your Emergency Kit contains the information you’ll need to
sign in to your Infisical account.
</p>
<p className="text-sm text-mineshaft-300 mb-5 min-w-max">
Only the latest issued Emergency Kit remains valid. To get a
new Emergency Kit, verify your password.
</p>
</div>
</div>
<div className="w-full max-w-xl mb-4">
<InputField
label="Current Password"
onChangeHandler={setBackupPassword}
type="password"
value={backupPassword}
isRequired
error={backupKeyError}
errorText="The current password is wrong"
/>
</div>
<div className="flex flex-row items-center mt-3 w-full w-60">
<Button
text="Download Emergency Kit"
onButtonPressed={() => {
issueBackupKey({
email: personalEmail,
password: backupPassword,
personalName,
setBackupKeyError,
setBackupKeyIssued,
});
}}
color="mineshaft"
size="md"
active={backupPassword != ""}
textDisabled="Download Emergency Kit"
/>
<FontAwesomeIcon
icon={faCheck}
className={`ml-4 text-primary text-3xl ${
backupKeyIssued ? "opacity-100" : "opacity-0"
} duration-300`}
/>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
PersonalSettings.requireAuth = true;
+222 -228
View File
@@ -1,206 +1,205 @@
import React, { useState, useRef, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect, useRef, useState } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
import InputField from "../../../components/basic/InputField";
import Button from "~/components/basic/buttons/Button";
import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDialog";
import InputField from "~/components/basic/InputField";
import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable";
import NavHeader from "~/components/navigation/NavHeader";
import getServiceTokens from "../../api/serviceToken/getServiceTokens";
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import getWorkspaces from "../../api/workspace/getWorkspaces";
import renameWorkspace from "../../api/workspace/renameWorkspace";
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import NavHeader from "../../../components/navigation/NavHeader";
import Button from "../../../components/basic/buttons/Button";
import ServiceTokenTable from "../../../components/basic/table/ServiceTokenTable";
import getServiceTokens from "../../api/serviceToken/getServiceTokens"
import AddServiceTokenDialog from "../../../components/basic/dialog/AddServiceTokenDialog";
export default function SettingsBasic() {
const [buttonReady, setButtonReady] = useState(false);
const router = useRouter();
const [workspaceName, setWorkspaceName] = useState("");
const [serviceTokens, setServiceTokens] = useState([]);
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] =
useState("");
const [workspaceId, setWorkspaceId] = useState("");
const [isAddOpen, setIsAddOpen] = useState(false);
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] = useState(false);
const [buttonReady, setButtonReady] = useState(false);
const router = useRouter();
const [workspaceName, setWorkspaceName] = useState("");
const [serviceTokens, setServiceTokens] = useState([]);
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState("");
const [workspaceId, setWorkspaceId] = useState("");
const [isAddOpen, setIsAddOpen] = useState(false);
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] =
useState(false);
useEffect(async () => {
let userWorkspaces = await getWorkspaces();
userWorkspaces.map((userWorkspace) => {
if (userWorkspace._id == router.query.id) {
setWorkspaceName(userWorkspace.name);
}
});
let tempServiceTokens = await getServiceTokens({workspaceId: router.query.id});
setServiceTokens(tempServiceTokens);
}, []);
useEffect(async () => {
let userWorkspaces = await getWorkspaces();
userWorkspaces.map((userWorkspace) => {
if (userWorkspace._id == router.query.id) {
setWorkspaceName(userWorkspace.name);
}
});
let tempServiceTokens = await getServiceTokens({
workspaceId: router.query.id,
});
setServiceTokens(tempServiceTokens);
}, []);
const modifyWorkspaceName = (newName) => {
setButtonReady(true);
setWorkspaceName(newName);
};
const modifyWorkspaceName = (newName) => {
setButtonReady(true);
setWorkspaceName(newName);
};
const submitChanges = (newWorkspaceName) => {
renameWorkspace(router.query.id, newWorkspaceName);
setButtonReady(false);
};
const submitChanges = (newWorkspaceName) => {
renameWorkspace(router.query.id, newWorkspaceName);
setButtonReady(false);
};
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
function closeAddModal() {
setIsAddOpen(false);
}
function closeAddModal() {
setIsAddOpen(false);
}
function openAddModal() {
setIsAddOpen(true);
}
function openAddModal() {
setIsAddOpen(true);
}
const closeAddServiceTokenModal = () => {
setIsAddServiceTokenDialogOpen(false);
}
/**
* This function deleted a workspace.
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
* It then deletes the workspace and forwards the user to another aviable workspace.
*/
const executeDeletingWorkspace = async () => {
let userWorkspaces = await getWorkspaces();
const closeAddServiceTokenModal = () => {
setIsAddServiceTokenDialogOpen(false);
};
if (userWorkspaces.length > 1) {
if (
userWorkspaces.filter(
(workspace) => workspace._id == router.query.id
)[0].name == workspaceToBeDeletedName
) {
await deleteWorkspace(router.query.id);
let userWorkspaces = await getWorkspaces();
router.push("/dashboard/" + userWorkspaces[0]._id);
}
}
};
/**
* This function deleted a workspace.
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
* It then deletes the workspace and forwards the user to another aviable workspace.
*/
const executeDeletingWorkspace = async () => {
let userWorkspaces = await getWorkspaces();
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Settings</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<AddServiceTokenDialog
isOpen={isAddServiceTokenDialogOpen}
workspaceId={router.query.id}
closeModal={closeAddServiceTokenModal}
workspaceName={workspaceName}
/>
<div className="flex flex-row mr-6 max-w-5xl">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Project Settings" isProjectRelated={true}/>
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
Project Settings
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
These settings only apply to the currently
selected Project.
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<div className="flex flex-col">
<div className="min-w-md mt-2 flex flex-col items-start">
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
<p className="text-xl font-semibold mb-4">
Display Name
</p>
<div className="max-h-28 w-full max-w-md mr-auto">
<InputField
onChangeHandler={
modifyWorkspaceName
}
type="varName"
value={workspaceName}
placeholder=""
isRequired
/>
</div>
<div className="flex justify-start w-full">
<div
className={`flex justify-start max-w-sm mt-4 mb-2`}
>
<Button
text="Save Changes"
onButtonPressed={() => submitChanges(workspaceName)}
color="mineshaft"
size="md"
active={buttonReady}
iconDisabled={faCheck}
textDisabled="Saved"
/>
</div>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
<p className="text-xl font-semibold self-start">
Project ID
</p>
<p className="text-base text-gray-400 font-normal self-start mt-4">
To integrate Infisical into your code
base and get automatic injection of
environmental variables, you should use
the following Project ID.
</p>
<p className="text-base text-gray-400 font-normal self-start">
For more guidance, including code
snipets for various languages and
frameworks, see{" "}
<a
href="https://infisical.com/docs/getting-started/introduction"
target="_blank"
rel="noopener"
className="text-primary hover:opacity-80 duration-200"
>
Infisical Docs.
</a>
</p>
<div className="max-h-28 w-ful">
<InputField
type="varName"
value={router.query.id}
placeholder=""
isRequired
static
text="This is your project's auto-generated unique identifier. It can't be changed."
/>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 flex flex-col items-start flex flex-col items-start w-full mt-4 mb-4 pt-2">
<div className="flex flex-row justify-between w-full">
<div className="flex flex-col w-full">
<p className="text-xl font-semibold mb-3">
Service Tokens
</p>
<p className="text-base text-gray-400 mb-4">
Every service token is specific to you, a certain project and a certain environment within this project.
</p>
</div>
<div className="w-48">
<Button
text="Add New Token"
onButtonPressed={() => {setIsAddServiceTokenDialogOpen(true)}}
color="mineshaft"
icon={faPlus}
size="md"
/>
</div>
</div>
<ServiceTokenTable data={serviceTokens} workspaceName={workspaceName}/>
</div>
if (userWorkspaces.length > 1) {
if (
userWorkspaces.filter(
(workspace) => workspace._id == router.query.id
)[0].name == workspaceToBeDeletedName
) {
await deleteWorkspace(router.query.id);
let userWorkspaces = await getWorkspaces();
router.push("/dashboard/" + userWorkspaces[0]._id);
}
}
};
{/* <div className="bg-white/5 rounded-md px-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-6 pt-6">
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Settings</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<AddServiceTokenDialog
isOpen={isAddServiceTokenDialogOpen}
workspaceId={router.query.id}
closeModal={closeAddServiceTokenModal}
workspaceName={workspaceName}
/>
<div className="flex flex-row mr-6 max-w-5xl">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Project Settings" isProjectRelated={true} />
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
Project Settings
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
These settings only apply to the currently selected Project.
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<div className="flex flex-col">
<div className="min-w-md mt-2 flex flex-col items-start">
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
<p className="text-xl font-semibold mb-4">Display Name</p>
<div className="max-h-28 w-full max-w-md mr-auto">
<InputField
onChangeHandler={modifyWorkspaceName}
type="varName"
value={workspaceName}
placeholder=""
isRequired
/>
</div>
<div className="flex justify-start w-full">
<div className={`flex justify-start max-w-sm mt-4 mb-2`}>
<Button
text="Save Changes"
onButtonPressed={() => submitChanges(workspaceName)}
color="mineshaft"
size="md"
active={buttonReady}
iconDisabled={faCheck}
textDisabled="Saved"
/>
</div>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
<p className="text-xl font-semibold self-start">Project ID</p>
<p className="text-base text-gray-400 font-normal self-start mt-4">
To integrate Infisical into your code base and get automatic
injection of environmental variables, you should use the
following Project ID.
</p>
<p className="text-base text-gray-400 font-normal self-start">
For more guidance, including code snipets for various
languages and frameworks, see{" "}
{/* eslint-disable-next-line react/jsx-no-target-blank */}
<a
href="https://infisical.com/docs/getting-started/introduction"
target="_blank"
rel="noopener"
className="text-primary hover:opacity-80 duration-200"
>
Infisical Docs.
</a>
</p>
<div className="max-h-28 w-ful">
<InputField
type="varName"
value={router.query.id}
placeholder=""
isRequired
static
text="This is your project's auto-generated unique identifier. It can't be changed."
/>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 flex flex-col items-start flex flex-col items-start w-full mt-4 mb-4 pt-2">
<div className="flex flex-row justify-between w-full">
<div className="flex flex-col w-full">
<p className="text-xl font-semibold mb-3">
Service Tokens
</p>
<p className="text-base text-gray-400 mb-4">
Every service token is specific to you, a certain
project and a certain environment within this project.
</p>
</div>
<div className="w-48">
<Button
text="Add New Token"
onButtonPressed={() => {
setIsAddServiceTokenDialogOpen(true);
}}
color="mineshaft"
icon={faPlus}
size="md"
/>
</div>
</div>
<ServiceTokenTable
data={serviceTokens}
workspaceName={workspaceName}
/>
</div>
{/* <div className="bg-white/5 rounded-md px-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-6 pt-6">
<p className="text-xl font-semibold self-start">
Project Environments
</p>
@@ -235,47 +234,42 @@ export default function SettingsBasic() {
</div>
</div>
</div> */}
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
<p className="text-xl font-bold text-red">
Danger Zone
</p>
<p className="mt-2 text-md text-gray-400">
As soon as you delete this project, you will not
be able to undo it. This will immediately remove
all the keys. If you still want to do that,
please enter the name of the project below.
</p>
<div className="max-h-28 w-full max-w-md mr-auto mt-4">
<InputField
label="Project to be Deleted"
onChangeHandler={
setWorkspaceToBeDeletedName
}
type="varName"
value={workspaceToBeDeletedName}
placeholder=""
isRequired
/>
</div>
<button
type="button"
className="max-w-md mt-6 w-full inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2.5 text-sm font-medium text-gray-400 hover:bg-red hover:text-white hover:font-semibold hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={executeDeletingWorkspace}
>
Delete Project
</button>
<p className="mt-0.5 ml-1 text-xs text-gray-500">
Note: You can only delete a project in case you
have more than one.
</p>
</div>
</div>
</div>
</div>
</div>
);
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
<p className="text-xl font-bold text-red">Danger Zone</p>
<p className="mt-2 text-md text-gray-400">
As soon as you delete this project, you will not be able to undo
it. This will immediately remove all the keys. If you still want
to do that, please enter the name of the project below.
</p>
<div className="max-h-28 w-full max-w-md mr-auto mt-4">
<InputField
label="Project to be Deleted"
onChangeHandler={setWorkspaceToBeDeletedName}
type="varName"
value={workspaceToBeDeletedName}
placeholder=""
isRequired
/>
</div>
<button
type="button"
className="max-w-md mt-6 w-full inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2.5 text-sm font-medium text-gray-400 hover:bg-red hover:text-white hover:font-semibold hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={executeDeletingWorkspace}
>
Delete Project
</button>
<p className="mt-0.5 ml-1 text-xs text-gray-500">
Note: You can only delete a project in case you have more than
one.
</p>
</div>
</div>
</div>
</div>
</div>
);
}
SettingsBasic.requireAuth = true;
+496 -525
View File
File diff suppressed because it is too large Load Diff
+340 -280
View File
@@ -1,19 +1,19 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import InputField from "../components/basic/InputField";
import Button from "../components/basic/buttons/Button";
import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite";
import Aes256Gcm from "../components/aes-256-gcm";
import passwordCheck from "../components/utilities/checks/PasswordCheck";
import attemptLogin from "../components/utilities/attemptLogin";
import { useRouter } from "next/router";
import { faCheck, faWarning, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faX, faWarning } from "@fortawesome/free-solid-svg-icons";
import issueBackupKey from "../components/utilities/issueBackupKey";
import Button from "~/components/basic/buttons/Button";
import InputField from "~/components/basic/InputField";
import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm";
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
import attemptLogin from "~/utilities/attemptLogin";
import passwordCheck from "~/utilities/checks/PasswordCheck";
import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite";
import verifySignupInvite from "./api/auth/VerifySignupInvite";
const nacl = require("tweetnacl");
@@ -23,263 +23,323 @@ const client = new jsrp.client();
const queryString = require("query-string");
export default function SignupInvite() {
const [password, setPassword] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [firstNameError, setFirstNameError] = useState(false);
const [lastNameError, setLastNameError] = useState(false);
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const [email, setEmail] = useState(parsedUrl.to);
const token = parsedUrl.token;
const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [step, setStep] = useState(1);
const [backupKeyError, setBackupKeyError] = useState(false);
const [verificationToken, setVerificationToken] = useState();
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
const [password, setPassword] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [firstNameError, setFirstNameError] = useState(false);
const [lastNameError, setLastNameError] = useState(false);
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const [email, setEmail] = useState(parsedUrl.to);
const token = parsedUrl.token;
const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [step, setStep] = useState(1);
const [backupKeyError, setBackupKeyError] = useState(false);
const [verificationToken, setVerificationToken] = useState();
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
// Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.
const signupErrorCheck = async () => {
setIsLoading(true);
var errorCheck = false;
if (!firstName) {
setFirstNameError(true);
errorCheck = true;
} else {
setFirstNameError(false);
}
if (!lastName) {
setLastNameError(true);
errorCheck = true;
} else {
setLastNameError(false);
}
errorCheck = passwordCheck(
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
errorCheck
);
// Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.
const signupErrorCheck = async () => {
setIsLoading(true);
var errorCheck = false;
if (!firstName) {
setFirstNameError(true);
errorCheck = true;
} else {
setFirstNameError(false);
}
if (!lastName) {
setLastNameError(true);
errorCheck = true;
} else {
setLastNameError(false);
}
errorCheck = passwordCheck(
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
errorCheck
);
if (!errorCheck) {
// Generate a random pair of a public and a private key
const pair = nacl.box.keyPair();
const secretKeyUint8Array = pair.secretKey;
const publicKeyUint8Array = pair.publicKey;
const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array);
const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array);
if (!errorCheck) {
// Generate a random pair of a public and a private key
const pair = nacl.box.keyPair();
const secretKeyUint8Array = pair.secretKey;
const publicKeyUint8Array = pair.publicKey;
const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array);
const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array);
const { ciphertext, iv, tag } = Aes256Gcm.encrypt(
PRIVATE_KEY,
password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
);
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
const { ciphertext, iv, tag } = Aes256Gcm.encrypt(
PRIVATE_KEY,
password
.slice(0, 32)
.padStart(
32 + (password.slice(0, 32).length - new Blob([password]).size),
"0"
)
);
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
client.init(
{
username: email,
password: password,
},
async () => {
client.createVerifier(async (err, result) => {
const response = await completeAccountInformationSignupInvite({
email,
firstName,
lastName,
publicKey: PUBLIC_KEY,
ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken
});
client.init(
{
username: email,
password: password,
},
async () => {
client.createVerifier(async (err, result) => {
const response = await completeAccountInformationSignupInvite({
email,
firstName,
lastName,
publicKey: PUBLIC_KEY,
ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken,
});
// if everything works, go the main dashboard page.
if (!errorCheck && response.status == "200") {
response = await response.json();
// if everything works, go the main dashboard page.
if (!errorCheck && response.status == "200") {
response = await response.json();
localStorage.setItem("publicKey", PUBLIC_KEY);
localStorage.setItem(
"encryptedPrivateKey",
ciphertext
);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
localStorage.setItem("publicKey", PUBLIC_KEY);
localStorage.setItem("encryptedPrivateKey", ciphertext);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
try {
await attemptLogin(
email,
password,
setErrorLogin,
router,
false,
false
);
setStep(3);
} catch (error) {
setIsLoading(false);
console.log("Error", error);
}
}
});
}
);
} else {
setIsLoading(false);
}
};
try {
await attemptLogin(
email,
password,
setErrorLogin,
router,
false,
false
);
setStep(3);
} catch (error) {
setIsLoading(false);
console.log("Error", error);
}
}
});
}
);
} else {
setIsLoading(false);
}
};
// Step 4 of the sign up process (download the emergency kit pdf)
const stepConfirmEmail = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold mb-8 flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
Confirm your email
</p>
<Image
src="/images/envelope.svg"
height={262}
width={410}
alt="verify email"
></Image>
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto text-lg py-1 text-center md:text-left">
<Button
text="Confirm Email"
onButtonPressed={async () => {
const response = await verifySignupInvite({
email,
code: token
});
if (response.status == 200) {
setVerificationToken((await response.json()).token)
setStep(2);
} else {
console.log("ERROR", response)
router.push("/requestnewinvite")
}
}}
size="lg"
/>
</div>
</div>
);
// Step 4 of the sign up process (download the emergency kit pdf)
const stepConfirmEmail = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold mb-8 flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
Confirm your email
</p>
<Image
src="/images/envelope.svg"
height={262}
width={410}
alt="verify email"
></Image>
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto text-lg py-1 text-center md:text-left">
<Button
text="Confirm Email"
onButtonPressed={async () => {
const response = await verifySignupInvite({
email,
code: token,
});
if (response.status == 200) {
setVerificationToken((await response.json()).token);
setStep(2);
} else {
console.log("ERROR", response);
router.push("/requestnewinvite");
}
}}
size="lg"
/>
</div>
</div>
);
// Because this is the invite signup - we directly go to the last step of signup (email is already verified)
const main = (
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 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">
Almost there!
</p>
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
<InputField
label="First Name"
onChangeHandler={setFirstName}
type="name"
value={firstName}
isRequired
errorText="Please input your first name."
error={firstNameError}
/>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24">
<InputField
label="Last Name"
onChangeHandler={setLastName}
type="name"
value={lastName}
isRequired
errorText="Please input your last name."
error={lastNameError}
/>
</div>
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
<InputField
label="Password"
onChangeHandler={(password) => {
setPassword(password);
passwordCheck(
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
false
);
}}
type="password"
value={password}
isRequired
error={passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase}
/>
{(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber) ? <div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
<div className={`text-gray-400 text-sm mb-1`}>Password should contain at least:</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorLength ? "text-gray-400" : "text-gray-600"} text-sm`}>14 characters</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"} text-sm`}>1 lowercase character</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorNumber ? "text-gray-400" : "text-gray-600"} text-sm`}>1 number</div>
</div>
</div> : <div className="py-2"></div>}
</div>
<div className="flex flex-col items-center justify-center w-full md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-md mx-auto text-lg">
<Button
text="Sign Up"
onButtonPressed={() => {
signupErrorCheck();
}}
loading={isLoading}
size="lg"
/>
</div>
</div>
);
// Because this is the invite signup - we directly go to the last step of signup (email is already verified)
const main = (
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 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">
Almost there!
</p>
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
<InputField
label="First Name"
onChangeHandler={setFirstName}
type="name"
value={firstName}
isRequired
errorText="Please input your first name."
error={firstNameError}
/>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24">
<InputField
label="Last Name"
onChangeHandler={setLastName}
type="name"
value={lastName}
isRequired
errorText="Please input your last name."
error={lastNameError}
/>
</div>
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
<InputField
label="Password"
onChangeHandler={(password) => {
setPassword(password);
passwordCheck(
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
false
);
}}
type="password"
value={password}
isRequired
error={
passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase
}
/>
{passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber ? (
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
<div className={`text-gray-400 text-sm mb-1`}>
Password should contain at least:
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLength ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
14 characters
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
1 lowercase character
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
1 number
</div>
</div>
</div>
) : (
<div className="py-2"></div>
)}
</div>
<div className="flex flex-col items-center justify-center w-full md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-md mx-auto text-lg">
<Button
text="Sign Up"
onButtonPressed={() => {
signupErrorCheck();
}}
loading={isLoading}
size="lg"
/>
</div>
</div>
);
// Step 4 of the sign up process (download the emergency kit pdf)
const step4 = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
Save your Emergency Kit
</p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
<div>If you get locked out of your account, your Emergency Kit is the only way to sign in.</div>
<div className="mt-3">We recommend you download it and keep it somewhere safe.</div>
</div>
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl"/>
It contains your Secret Key which we cannot access or recover for you if you lose it.
</div>
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-6 md:mt-8 py-1 text-lg text-center md:text-left">
<Button
text="Download PDF"
onButtonPressed={async () => {
await issueBackupKey({
email,
password,
personalName: (firstName + " " + lastName),
setBackupKeyError,
setBackupKeyIssued
});
router.push("/dashboard/");
}}
size="lg"
/>
{/* <div
// Step 4 of the sign up process (download the emergency kit pdf)
const step4 = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
Save your Emergency Kit
</p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
<div>
If you get locked out of your account, your Emergency Kit is the only
way to sign in.
</div>
<div className="mt-3">
We recommend you download it and keep it somewhere safe.
</div>
</div>
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
It contains your Secret Key which we cannot access or recover for you if
you lose it.
</div>
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-6 md:mt-8 py-1 text-lg text-center md:text-left">
<Button
text="Download PDF"
onButtonPressed={async () => {
await issueBackupKey({
email,
password,
personalName: firstName + " " + lastName,
setBackupKeyError,
setBackupKeyIssued,
});
router.push("/dashboard/");
}}
size="lg"
/>
{/* <div
className="text-l mt-4 text-lg text-gray-400 hover:text-gray-300 duration-200 bg-white/5 px-8 hover:bg-white/10 py-3 rounded-md cursor-pointer"
onClick={() => {
if (localStorage.getItem("projectData.id")) {
@@ -291,27 +351,27 @@ export default function SignupInvite() {
>
Later
</div> */}
</div>
</div>
);
</div>
</div>
);
return (
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
<Head>
<title>Sign Up</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<Link href="/">
<div className="flex justify-center mb-2 md:mb-4 opacity-80 cursor-pointer">
<Image
src="/images/biglogo.png"
height={90}
width={120}
alt="Infisical Wide Logo"
/>
</div>
</Link>
{step == 1 ? stepConfirmEmail : step == 2 ? main : step4}
</div>
);
return (
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
<Head>
<title>Sign Up</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<Link href="/">
<div className="flex justify-center mb-2 md:mb-4 opacity-80 cursor-pointer">
<Image
src="/images/biglogo.png"
height={90}
width={120}
alt="Infisical Wide Logo"
/>
</div>
</Link>
{step == 1 ? stepConfirmEmail : step == 2 ? main : step4}
</div>
);
}
+183 -189
View File
@@ -1,217 +1,211 @@
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMagnifyingGlass, faPlus } from "@fortawesome/free-solid-svg-icons";
import UserTable from "../../components/basic/table/UserTable";
import getWorkspaceUsers from "../api/workspace/getWorkspaceUsers";
import guidGenerator from "../../components/utilities/randomId";
import AddProjectMemberDialog from "../../components/basic/dialog/AddProjectMemberDialog";
// import DeleteUserDialog from '../../components/basic/dialog/DeleteUserDialog';
import addUserToWorkspace from "../api/workspace/addUserToWorkspace";
import getUser from "../api/user/getUser";
import uploadKeys from "../api/workspace/uploadKeys";
import getOrganizationUsers from "../api/organization/GetOrgUsers";
import NavHeader from "../../components/navigation/NavHeader";
import Button from "../../components/basic/buttons/Button";
import { useRouter } from "next/router";
import useTranslation from "next-translate/useTranslation";
import { faMagnifyingGlass, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button";
import AddProjectMemberDialog from "~/components/basic/dialog/AddProjectMemberDialog";
import UserTable from "~/components/basic/table/UserTable";
import NavHeader from "~/components/navigation/NavHeader";
import guidGenerator from "~/utilities/randomId";
import getOrganizationUsers from "../api/organization/GetOrgUsers";
import getUser from "../api/user/getUser";
// import DeleteUserDialog from '~/components/basic/dialog/DeleteUserDialog';
import addUserToWorkspace from "../api/workspace/addUserToWorkspace";
import getWorkspaceUsers from "../api/workspace/getWorkspaceUsers";
import uploadKeys from "../api/workspace/uploadKeys";
// #TODO: Update all the workspaceIds
const crypto = require("crypto");
const {
decryptAssymmetric,
encryptAssymmetric,
} = require("../../components/utilities/crypto");
decryptAssymmetric,
encryptAssymmetric,
} = require("../../components/utilities/cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
export default function Users() {
let [isAddOpen, setIsAddOpen] = useState(false);
// let [isDeleteOpen, setIsDeleteOpen] = useState(false);
// let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false);
let [email, setEmail] = useState("");
const [personalEmail, setPersonalEmail] = useState("");
const [searchUsers, setSearchUsers] = useState("");
let [isAddOpen, setIsAddOpen] = useState(false);
// let [isDeleteOpen, setIsDeleteOpen] = useState(false);
// let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false);
let [email, setEmail] = useState("");
const [personalEmail, setPersonalEmail] = useState("");
const [searchUsers, setSearchUsers] = useState("");
const { t } = useTranslation();
const { t } = useTranslation();
const router = useRouter();
let workspaceId;
const router = useRouter();
let workspaceId;
function closeAddModal() {
setIsAddOpen(false);
}
function closeAddModal() {
setIsAddOpen(false);
}
// function closeDeleteModal() {
// setIsDeleteOpen(false);
// }
// function closeDeleteModal() {
// setIsDeleteOpen(false);
// }
// function deleteMembership(userId) {
// deleteUserFromWorkspace(userId, router.query.id)
// }
// function deleteMembership(userId) {
// deleteUserFromWorkspace(userId, router.query.id)
// }
// function openDeleteModal() {
// setIsDeleteOpen(true);
// }
// function openDeleteModal() {
// setIsDeleteOpen(true);
// }
async function submitAddModal() {
let result = await addUserToWorkspace(email, router.query.id);
if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
async function submitAddModal() {
let result = await addUserToWorkspace(email, router.query.id);
if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
});
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: result.invitee.publicKey,
privateKey: PRIVATE_KEY,
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: result.invitee.publicKey,
privateKey: PRIVATE_KEY,
});
uploadKeys(router.query.id, result.invitee._id, ciphertext, nonce);
}
setEmail("");
setIsAddOpen(false);
router.reload();
}
uploadKeys(router.query.id, result.invitee._id, ciphertext, nonce);
}
setEmail("");
setIsAddOpen(false);
router.reload();
}
function openAddModal() {
setIsAddOpen(true);
}
function openAddModal() {
setIsAddOpen(true);
}
const [userList, setUserList] = useState();
const [orgUserList, setOrgUserList] = useState([]);
useEffect(async () => {
const user = await getUser();
setPersonalEmail(user.email);
const [userList, setUserList] = useState();
const [orgUserList, setOrgUserList] = useState([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
const user = await getUser();
setPersonalEmail(user.email);
workspaceId = router.query.id;
let workspaceUsers = await getWorkspaceUsers({
workspaceId,
});
const tempUserList = workspaceUsers.map((user) => ({
key: guidGenerator(),
firstName: user.user?.firstName,
lastName: user.user?.lastName,
email:
user.user?.email == null ? user.inviteEmail : user.user?.email,
role: user?.role,
status: user?.status,
userId: user.user?._id,
membershipId: user._id,
publicKey: user.user?.publicKey,
}));
setUserList(tempUserList);
const orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
setOrgUserList(orgUsers);
setEmail(
orgUsers
?.filter((user) => user.status == "accepted")
.map((user) => user.user.email)
.filter(
(email) =>
!tempUserList
?.map((user1) => user1.email)
.includes(email)
)[0]
);
}, []);
workspaceId = router.query.id;
let workspaceUsers = await getWorkspaceUsers({
workspaceId,
});
const tempUserList = workspaceUsers.map((user) => ({
key: guidGenerator(),
firstName: user.user?.firstName,
lastName: user.user?.lastName,
email: user.user?.email == null ? user.inviteEmail : user.user?.email,
role: user?.role,
status: user?.status,
userId: user.user?._id,
membershipId: user._id,
publicKey: user.user?.publicKey,
}));
setUserList(tempUserList);
const orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
setOrgUserList(orgUsers);
setEmail(
orgUsers
?.filter((user) => user.status == "accepted")
.map((user) => user.user.email)
.filter(
(email) => !tempUserList?.map((user1) => user1.email).includes(email)
)[0]
);
}, []);
return userList ? (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-start">
<Head>
<title>{t("settings:meta.members.head-title")}</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<NavHeader
pageName={t("settings:meta.members.title")}
isProjectRelated={true}
/>
<div className="flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl">
<p className="font-semibold mr-4 text-white">
{t("settings:meta.members.title")}
</p>
<p className="mr-4 text-base text-gray-400">
{t("settings:meta.members.description")}
</p>
</div>
<AddProjectMemberDialog
isOpen={isAddOpen}
closeModal={closeAddModal}
submitModal={submitAddModal}
email={email}
data={orgUserList
?.filter((user) => user.status == "accepted")
.map((user) => user.user.email)
.filter(
(email) =>
!userList
?.map((user1) => user1.email)
.includes(email)
)}
workspaceId={workspaceId}
setEmail={setEmail}
/>
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
<div className="px-2 pb-1 w-full flex flex-row items-start max-w-5xl">
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center ml-4">
<FontAwesomeIcon
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
icon={faMagnifyingGlass}
/>
<input
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchUsers}
onChange={(e) => setSearchUsers(e.target.value)}
placeholder={t("settings:search-members")}
/>
</div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start mr-4">
<Button
text={t("settings:add-member")}
onButtonPressed={openAddModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
</div>
</div>
<div className="overflow-y-auto max-w-5xl mx-6">
<UserTable
userData={userList}
changeData={setUserList}
myUser={personalEmail}
filter={searchUsers}
resendInvite={submitAddModal}
isOrg={false}
// onClick={openDeleteModal}
// deleteUser={deleteMembership}
// setUserIdToBeDeleted={setUserIdToBeDeleted}
className="w-full mx-6"
/>
</div>
</div>
) : (
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
<Image
src="/images/loading/loading.gif"
height={70}
width={120}
alt="loading animation"
></Image>
</div>
);
return userList ? (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-start">
<Head>
<title>{t("settings:meta.members.head-title")}</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<NavHeader
pageName={t("settings:meta.members.title")}
isProjectRelated={true}
/>
<div className="flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl">
<p className="font-semibold mr-4 text-white">
{t("settings:meta.members.title")}
</p>
<p className="mr-4 text-base text-gray-400">
{t("settings:meta.members.description")}
</p>
</div>
<AddProjectMemberDialog
isOpen={isAddOpen}
closeModal={closeAddModal}
submitModal={submitAddModal}
email={email}
data={orgUserList
?.filter((user) => user.status == "accepted")
.map((user) => user.user.email)
.filter(
(email) => !userList?.map((user1) => user1.email).includes(email)
)}
workspaceId={workspaceId}
setEmail={setEmail}
/>
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
<div className="px-2 pb-1 w-full flex flex-row items-start max-w-5xl">
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center ml-4">
<FontAwesomeIcon
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
icon={faMagnifyingGlass}
/>
<input
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchUsers}
onChange={(e) => setSearchUsers(e.target.value)}
placeholder={t("settings:search-members")}
/>
</div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start mr-4">
<Button
text={t("settings:add-member")}
onButtonPressed={openAddModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
</div>
</div>
<div className="overflow-y-auto max-w-5xl mx-6">
<UserTable
userData={userList}
changeData={setUserList}
myUser={personalEmail}
filter={searchUsers}
resendInvite={submitAddModal}
isOrg={false}
// onClick={openDeleteModal}
// deleteUser={deleteMembership}
// setUserIdToBeDeleted={setUserIdToBeDeleted}
className="w-full mx-6"
/>
</div>
</div>
) : (
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
<Image
src="/images/loading/loading.gif"
height={70}
width={120}
alt="loading animation"
></Image>
</div>
);
}
Users.requireAuth = true;