diff --git a/.github/values.yaml b/.github/values.yaml index 62afb494f..819df5066 100644 --- a/.github/values.yaml +++ b/.github/values.yaml @@ -2,7 +2,8 @@ frontend: enabled: true name: frontend podAnnotations: {} - deploymentAnnotations: {} + deploymentAnnotations: + secrets.infisical.com/auto-reload: "true" replicaCount: 2 image: repository: infisical/frontend @@ -20,7 +21,8 @@ backend: enabled: true name: backend podAnnotations: {} - deploymentAnnotations: {} + deploymentAnnotations: + secrets.infisical.com/auto-reload: "true" replicaCount: 2 image: repository: infisical/backend diff --git a/README.md b/README.md index 9f2590080..131d055b4 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ - **[Activity Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** to record every action taken in a project - **[Point-in-time Secrets Recovery](https://infisical.com/docs/getting-started/dashboard/pit-recovery)** for rolling back to any snapshot of your secrets - **Role-based Access Controls** per environment -- 🔜 **2FA** (next week) +- **2FA** (more options coming soon) - 🔜 **1-Click Deploy** to AWS - 🔜 **Automatic Secret Rotation** - 🔜 **Smart Security Alerts** diff --git a/backend/src/controllers/v2/authController.ts b/backend/src/controllers/v2/authController.ts index fa41eda73..95c359dd4 100644 --- a/backend/src/controllers/v2/authController.ts +++ b/backend/src/controllers/v2/authController.ts @@ -4,7 +4,7 @@ import jwt from 'jsonwebtoken'; import * as Sentry from '@sentry/node'; import * as bigintConversion from 'bigint-conversion'; const jsrp = require('jsrp'); -import { User } from '../../models'; +import { User, LoginSRPDetail } from '../../models'; import { issueAuthTokens, createToken } from '../../helpers/auth'; import { sendMail } from '../../helpers/nodemailer'; import { TokenService } from '../../services'; @@ -13,6 +13,7 @@ import { JWT_MFA_LIFETIME, JWT_MFA_SECRET } from '../../config'; +import { BadRequestError } from '../../utils/errors'; import { TOKEN_EMAIL_MFA } from '../../variables'; @@ -50,13 +51,15 @@ export const login1 = async (req: Request, res: Response) => { salt: user.salt, verifier: user.verifier }, - () => { + async () => { // generate server-side public key const serverPublicKey = server.getPublicKey(); - clientPublicKeys[email] = { - clientPublicKey, - serverBInt: bigintConversion.bigintToBuf(server.bInt) - }; + + await LoginSRPDetail.findOneAndReplace({ email: email }, { + email: email, + clientPublicKey: clientPublicKey, + serverBInt: bigintConversion.bigintToBuf(server.bInt), + }, { upsert: true, returnNewDocument: false }); return res.status(200).send({ serverPublicKey, @@ -89,15 +92,21 @@ export const login2 = async (req: Request, res: Response) => { if (!user) throw new Error('Failed to find user'); + const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email }) + + if (!loginSRPDetail) { + return BadRequestError(Error("Failed to find login details for SRP")) + } + const server = new jsrp.server(); server.init( { salt: user.salt, verifier: user.verifier, - b: clientPublicKeys[email].serverBInt + b: loginSRPDetail.serverBInt }, async () => { - server.setClientPublicKey(clientPublicKeys[email].clientPublicKey); + server.setClientPublicKey(loginSRPDetail.clientPublicKey); // compare server and client shared keys if (server.checkClientProof(clientProof)) { diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 1e08a701f..301259198 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -138,7 +138,7 @@ const exchangeCodeAzure = async ({ try { res = (await axios.post( INTEGRATION_AZURE_TOKEN_URL, - new URLSearchParams({ + new URLSearchParams({ grant_type: 'authorization_code', code: code, scope: 'https://vault.azure.net/.default openid offline_access', @@ -147,16 +147,16 @@ const exchangeCodeAzure = async ({ redirect_uri: `${SITE_URL}/integrations/azure-key-vault/oauth2/callback` } as any) )).data; - + accessExpiresAt.setSeconds( - accessExpiresAt.getSeconds() + res.expires_in + accessExpiresAt.getSeconds() + res.expires_in ); } catch (err: any) { Sentry.setUser(null); Sentry.captureException(err); throw new Error('Failed OAuth2 code-token exchange with Azure'); } - + return ({ accessToken: res.access_token, refreshToken: res.refresh_token, @@ -175,36 +175,36 @@ const exchangeCodeAzure = async ({ * @returns {Date} obj2.accessExpiresAt - date of expiration for access token */ const exchangeCodeHeroku = async ({ - code + code }: { - code: string; + code: string; }) => { - let res: ExchangeCodeHerokuResponse; - const accessExpiresAt = new Date(); - try { - res = (await axios.post( - INTEGRATION_HEROKU_TOKEN_URL, - new URLSearchParams({ - grant_type: 'authorization_code', - code: code, - client_secret: CLIENT_SECRET_HEROKU - } as any) - )).data; - - accessExpiresAt.setSeconds( - accessExpiresAt.getSeconds() + res.expires_in - ); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed OAuth2 code-token exchange with Heroku'); - } - - return ({ - accessToken: res.access_token, - refreshToken: res.refresh_token, - accessExpiresAt - }); + let res: ExchangeCodeHerokuResponse; + const accessExpiresAt = new Date(); + try { + res = (await axios.post( + INTEGRATION_HEROKU_TOKEN_URL, + new URLSearchParams({ + grant_type: 'authorization_code', + code: code, + client_secret: CLIENT_SECRET_HEROKU + } as any) + )).data; + + accessExpiresAt.setSeconds( + accessExpiresAt.getSeconds() + res.expires_in + ); + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed OAuth2 code-token exchange with Heroku'); + } + + return ({ + accessToken: res.access_token, + refreshToken: res.refresh_token, + accessExpiresAt + }); } /** @@ -234,7 +234,7 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => { } catch (err) { Sentry.setUser(null); Sentry.captureException(err); - throw new Error('Failed OAuth2 code-token exchange with Vercel'); + throw new Error(`Failed OAuth2 code-token exchange with Vercel [err=${err}]`); } return { diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index e4bdc116c..3309e5813 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -30,7 +30,6 @@ import { INTEGRATION_FLYIO_API_URL, INTEGRATION_CIRCLECI_API_URL, } from "../variables"; -import { access, appendFile } from "fs"; /** * Sync/push [secrets] to [app] in integration named [integration] @@ -181,7 +180,8 @@ const syncSecretsAzureKeyVault = async ({ while (url) { const res = await axios.get(url, { headers: { - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } }); @@ -202,7 +202,8 @@ const syncSecretsAzureKeyVault = async ({ const azureKeyVaultSecret = await axios.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, { headers: { - 'Authorization': `Bearer ${accessToken}` + 'Authorization': `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } }); @@ -259,7 +260,8 @@ const syncSecretsAzureKeyVault = async ({ }, { headers: { - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } } ); @@ -270,7 +272,8 @@ const syncSecretsAzureKeyVault = async ({ deleteSecrets.forEach(async (secret) => { await axios.delete(`${integration.app}/secrets/${secret.key}?api-version=7.3`, { headers: { - 'Authorization': `Bearer ${accessToken}` + 'Authorization': `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } }); }); @@ -488,6 +491,7 @@ const syncSecretsHeroku = async ({ headers: { Accept: "application/vnd.heroku+json; version=3", Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ) @@ -506,6 +510,7 @@ const syncSecretsHeroku = async ({ headers: { Accept: "application/vnd.heroku+json; version=3", Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -552,7 +557,7 @@ const syncSecretsVercel = async ({ } : {}), }; - + const res = ( await Promise.all( ( @@ -561,7 +566,8 @@ const syncSecretsVercel = async ({ { params, headers: { - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } } )) @@ -573,7 +579,8 @@ const syncSecretsVercel = async ({ { params, headers: { - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } } )).data) @@ -633,6 +640,7 @@ const syncSecretsVercel = async ({ params, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -649,6 +657,7 @@ const syncSecretsVercel = async ({ params, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -664,6 +673,7 @@ const syncSecretsVercel = async ({ params, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -723,6 +733,7 @@ const syncSecretsNetlify = async ({ params: getParams, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ) @@ -837,6 +848,7 @@ const syncSecretsNetlify = async ({ params: syncParams, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -854,6 +866,7 @@ const syncSecretsNetlify = async ({ params: syncParams, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -868,6 +881,7 @@ const syncSecretsNetlify = async ({ params: syncParams, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -882,6 +896,7 @@ const syncSecretsNetlify = async ({ params: syncParams, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -1035,6 +1050,7 @@ const syncSecretsRender = async ({ { headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -1088,6 +1104,7 @@ const syncSecretsFlyio = async ({ method: "post", headers: { Authorization: "Bearer " + accessToken, + 'Accept-Encoding': 'application/json' }, data: { query: SetSecrets, @@ -1167,6 +1184,7 @@ const syncSecretsFlyio = async ({ headers: { Authorization: "Bearer " + accessToken, "Content-Type": "application/json", + 'Accept-Encoding': 'application/json' }, data: { query: DeleteSecrets, diff --git a/frontend/public/data/frequentInterfaces.ts b/frontend/public/data/frequentInterfaces.ts index fa6c73a57..953096e1d 100644 --- a/frontend/public/data/frequentInterfaces.ts +++ b/frontend/public/data/frequentInterfaces.ts @@ -13,6 +13,7 @@ export interface SecretDataProps { value: string | undefined; valueOverride: string | undefined; id: string; + idOverride?: string; comment: string; tags: Tag[]; } \ No newline at end of file diff --git a/frontend/src/components/basic/table/ProjectUsersTable.tsx b/frontend/src/components/basic/table/ProjectUsersTable.tsx index 27346195c..438517aa5 100644 --- a/frontend/src/components/basic/table/ProjectUsersTable.tsx +++ b/frontend/src/components/basic/table/ProjectUsersTable.tsx @@ -30,7 +30,7 @@ type Props = { type EnvironmentProps = { name: string; slug: string; -} +}; /** * This is the component that shows the users of a certin project @@ -91,26 +91,38 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { }); }; - const handlePermissionUpdate = (index: number, val: string, membershipId: string, slug: string ) => { - let denials: { ability: string; environmentSlug: string; }[]; - if (val === "Read Only") { - denials = [{ - ability: "write", - environmentSlug: slug - }]; - } else if (val === "No Access") { - denials = [{ - ability: "write", - environmentSlug: slug - }, { - ability: "read", - environmentSlug: slug - }]; - } else if (val === "Add Only") { - denials = [{ - ability: "read", - environmentSlug: slug - }]; + const handlePermissionUpdate = ( + index: number, + val: string, + membershipId: string, + slug: string + ) => { + let denials: { ability: string; environmentSlug: string }[]; + if (val === 'Read Only') { + denials = [ + { + ability: 'write', + environmentSlug: slug + } + ]; + } else if (val === 'No Access') { + denials = [ + { + ability: 'write', + environmentSlug: slug + }, + { + ability: 'read', + environmentSlug: slug + } + ]; + } else if (val === 'Add Only') { + denials = [ + { + ability: 'read', + environmentSlug: slug + } + ]; } else { denials = []; } @@ -118,8 +130,12 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { if (currentPlan !== plans.professional && host === 'https://app.infisical.com') { setIsUpgradeModalOpen(true); } else { - const allDenials = userData[index].deniedPermissions.filter((perm: { ability: string; environmentSlug: string; }) => perm.environmentSlug !== slug).concat(denials); - updateUserProjectPermission({ membershipId, denials: allDenials}); + const allDenials = userData[index].deniedPermissions + .filter( + (perm: { ability: string; environmentSlug: string }) => perm.environmentSlug !== slug + ) + .concat(denials); + updateUserProjectPermission({ membershipId, denials: allDenials }); changeData([ ...userData.slice(0, index), ...[ @@ -156,7 +172,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { orgId }); if (subscriptions) { - setCurrentPlan(subscriptions.data[0].plan.product) + setCurrentPlan(subscriptions.data[0].plan.product); } })(); }, [userData, myUser]); @@ -186,25 +202,28 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { const closeUpgradeModal = () => { setIsUpgradeModalOpen(false); - } + }; return ( -
| NAME | -ROLE | - {workspaceEnvs.map(env => ( -
- {env.slug.toUpperCase()} + | NAME | +ROLE | + {workspaceEnvs.map((env) => ( +
+
+ {env.slug.toUpperCase()}
+ + {/* PERMISSION */} |
))}
@@ -227,28 +246,28 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
)
.map((row, index) => (
|||
|---|---|---|---|---|---|---|---|---|
| + | {row.firstName} {row.lastName} | -+ | {row.email} | -
-
- |
- {workspaceEnvs.map((env) =>
- |
- | )}
-
+ |
+ ))}
+
{myUser !== row.email &&
// row.role !== "admin" &&
myRole !== 'member' ? (
- |
+ |
| + | |
|---|---|
| {email} | +
+ |
+
| Name | +Role | +Projects | ++ | |
|---|---|---|---|---|
| {name} | +{email} | +
+ {status === 'accepted' && (
+ |
+
+ {userWs ? (
+ userWs?.map(({ name: wsName, _id }) => (
+ |
+
+ {userId !== user?._id && |
+