Merge branch 'main' into feature/google-signin-signup-integration

This commit is contained in:
Sheen Capadngan
2023-04-27 01:46:26 +08:00
57 changed files with 784 additions and 411 deletions
+43 -7
View File
@@ -34,7 +34,7 @@
"express-validator": "^6.14.2", "express-validator": "^6.14.2",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"helmet": "^5.1.1", "helmet": "^5.1.1",
"infisical-node": "^1.0.37", "infisical-node": "^1.1.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4", "jsrp": "^0.2.4",
@@ -5345,6 +5345,14 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/co": { "node_modules/co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -6941,11 +6949,13 @@
} }
}, },
"node_modules/infisical-node": { "node_modules/infisical-node": {
"version": "1.0.37", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.0.37.tgz", "resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.1.3.tgz",
"integrity": "sha512-9ZswN5UovZq46a7Qv/4KmfaAu9pO/TmxxdcEY2PosDIAlXbpfC651hcKr7T8q1iMlRnHLA/gpYkcZMeS0es0rg==", "integrity": "sha512-MLcZQ/zdpCYFRbj50Tn4Qm58wSKPQfKc3xX4I0c3NnFZvMGd50wnoG1jkkNKjKiYU5h7QDpOg0XZSvlU7yuG6g==",
"dependencies": { "dependencies": {
"axios": "^1.3.3", "axios": "^1.3.3",
"dotenv": "^16.0.3",
"node-cache": "^5.1.2",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1" "tweetnacl-util": "^0.15.1"
} }
@@ -8439,6 +8449,17 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
}, },
"node_modules/node-cache": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
"dependencies": {
"clone": "2.x"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
@@ -17402,6 +17423,11 @@
"wrap-ansi": "^7.0.0" "wrap-ansi": "^7.0.0"
} }
}, },
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
},
"co": { "co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -18622,11 +18648,13 @@
"dev": true "dev": true
}, },
"infisical-node": { "infisical-node": {
"version": "1.0.37", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.0.37.tgz", "resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.1.3.tgz",
"integrity": "sha512-9ZswN5UovZq46a7Qv/4KmfaAu9pO/TmxxdcEY2PosDIAlXbpfC651hcKr7T8q1iMlRnHLA/gpYkcZMeS0es0rg==", "integrity": "sha512-MLcZQ/zdpCYFRbj50Tn4Qm58wSKPQfKc3xX4I0c3NnFZvMGd50wnoG1jkkNKjKiYU5h7QDpOg0XZSvlU7yuG6g==",
"requires": { "requires": {
"axios": "^1.3.3", "axios": "^1.3.3",
"dotenv": "^16.0.3",
"node-cache": "^5.1.2",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1" "tweetnacl-util": "^0.15.1"
} }
@@ -19774,6 +19802,14 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
}, },
"node-cache": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
"requires": {
"clone": "2.x"
}
},
"node-fetch": { "node-fetch": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
+2 -2
View File
@@ -3,7 +3,7 @@
"@aws-sdk/client-secrets-manager": "^3.303.0", "@aws-sdk/client-secrets-manager": "^3.303.0",
"@godaddy/terminus": "^4.11.2", "@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
"@sentry/node": "^7.45.0", "@sentry/node": "^7.41.0",
"@sentry/tracing": "^7.46.0", "@sentry/tracing": "^7.46.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10", "@types/libsodium-wrappers": "^0.7.10",
@@ -25,7 +25,7 @@
"express-validator": "^6.14.2", "express-validator": "^6.14.2",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"helmet": "^5.1.1", "helmet": "^5.1.1",
"infisical-node": "^1.0.37", "infisical-node": "^1.1.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4", "jsrp": "^0.2.4",
+63 -58
View File
@@ -1,67 +1,72 @@
import infisical from 'infisical-node'; import InfisicalClient from 'infisical-node';
export const getPort = () => infisical.get('PORT')! || 4000;
export const getInviteOnlySignup = () => infisical.get('INVITE_ONLY_SIGNUP')! == undefined ? false : infisical.get('INVITE_ONLY_SIGNUP'); const client = new InfisicalClient({
export const getEncryptionKey = () => infisical.get('ENCRYPTION_KEY')!; token: process.env.INFISICAL_TOKEN!
export const getSaltRounds = () => parseInt(infisical.get('SALT_ROUNDS')!) || 10; });
export const getJwtAuthLifetime = () => infisical.get('JWT_AUTH_LIFETIME')! || '10d';
export const getJwtAuthSecret = () => infisical.get('JWT_AUTH_SECRET')!; export const getPort = async () => (await client.getSecret('PORT')).secretValue || 4000;
export const getJwtMfaLifetime = () => infisical.get('JWT_MFA_LIFETIME')! || '5m'; export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue == undefined ? false : await client.getSecret('INVITE_ONLY_SIGNUP');
export const getJwtMfaSecret = () => infisical.get('JWT_MFA_LIFETIME')! || '5m'; export const getEncryptionKey = async () => (await client.getSecret('ENCRYPTION_KEY')).secretValue;
export const getJwtRefreshLifetime = () => infisical.get('JWT_REFRESH_LIFETIME')! || '90d'; export const getSaltRounds = async () => parseInt((await client.getSecret('SALT_ROUNDS')).secretValue) || 10;
export const getJwtRefreshSecret = () => infisical.get('JWT_REFRESH_SECRET')!; export const getJwtAuthLifetime = async () => (await client.getSecret('JWT_AUTH_LIFETIME')).secretValue || '10d';
export const getJwtServiceSecret = () => infisical.get('JWT_SERVICE_SECRET')!; export const getJwtAuthSecret = async () => (await client.getSecret('JWT_AUTH_SECRET')).secretValue;
export const getJwtSignupLifetime = () => infisical.get('JWT_SIGNUP_LIFETIME')! || '15m'; export const getJwtMfaLifetime = async () => (await client.getSecret('JWT_MFA_LIFETIME')).secretValue || '5m';
export const getJwtSignupSecret = () => infisical.get('JWT_SIGNUP_SECRET')!; export const getJwtMfaSecret = async () => (await client.getSecret('JWT_MFA_LIFETIME')).secretValue || '5m';
export const getJwtProviderAuthSecret = () => infisical.get('JWT_PROVIDER_AUTH_SECRET')!; export const getJwtRefreshLifetime = async () => (await client.getSecret('JWT_REFRESH_LIFETIME')).secretValue || '90d';
export const getJwtProviderAuthLifetime = () => infisical.get('JWT_PROVIDER_AUTH_LIFETIME')! || '15m'; export const getJwtRefreshSecret = async () => (await client.getSecret('JWT_REFRESH_SECRET')).secretValue;
export const getMongoURL = () => infisical.get('MONGO_URL')!; export const getJwtServiceSecret = async () => (await client.getSecret('JWT_SERVICE_SECRET')).secretValue;
export const getNodeEnv = () => infisical.get('NODE_ENV')! || 'production'; export const getJwtSignupLifetime = async () => (await client.getSecret('JWT_SIGNUP_LIFETIME')).secretValue || '15m';
export const getVerboseErrorOutput = () => infisical.get('VERBOSE_ERROR_OUTPUT')! === 'true' && true; export const getJwtProviderAuthSecret = async () => (await client.getSecret('JWT_PROVIDER_AUTH_SECRET')).secretValue;
export const getLokiHost = () => infisical.get('LOKI_HOST')!; export const getJwtProviderAuthLifetime = async () => (await client.getSecret('JWT_PROVIDER_AUTH_LIFETIME')).secretValue || '15m';
export const getClientIdAzure = () => infisical.get('CLIENT_ID_AZURE')!; export const getJwtSignupSecret = async () => (await client.getSecret('JWT_SIGNUP_SECRET')).secretValue;
export const getClientIdHeroku = () => infisical.get('CLIENT_ID_HEROKU')!; export const getMongoURL = async () => (await client.getSecret('MONGO_URL')).secretValue;
export const getClientIdVercel = () => infisical.get('CLIENT_ID_VERCEL')!; export const getNodeEnv = async () => (await client.getSecret('NODE_ENV')).secretValue || 'production';
export const getClientIdNetlify = () => infisical.get('CLIENT_ID_NETLIFY')!; export const getVerboseErrorOutput = async () => (await client.getSecret('VERBOSE_ERROR_OUTPUT')).secretValue === 'true' && true;
export const getClientIdGitHub = () => infisical.get('CLIENT_ID_GITHUB')!; export const getLokiHost = async () => (await client.getSecret('LOKI_HOST')).secretValue;
export const getClientIdGitLab = () => infisical.get('CLIENT_ID_GITLAB')!; export const getClientIdAzure = async () => (await client.getSecret('CLIENT_ID_AZURE')).secretValue;
export const getClientSecretAzure = () => infisical.get('CLIENT_SECRET_AZURE')!; export const getClientIdHeroku = async () => (await client.getSecret('CLIENT_ID_HEROKU')).secretValue;
export const getClientSecretHeroku = () => infisical.get('CLIENT_SECRET_HEROKU')!; export const getClientIdVercel = async () => (await client.getSecret('CLIENT_ID_VERCEL')).secretValue;
export const getClientSecretVercel = () => infisical.get('CLIENT_SECRET_VERCEL')!; export const getClientIdNetlify = async () => (await client.getSecret('CLIENT_ID_NETLIFY')).secretValue;
export const getClientSecretNetlify = () => infisical.get('CLIENT_SECRET_NETLIFY')!; export const getClientIdGitHub = async () => (await client.getSecret('CLIENT_ID_GITHUB')).secretValue;
export const getClientSecretGitHub = () => infisical.get('CLIENT_SECRET_GITHUB')!; export const getClientIdGitLab = async () => (await client.getSecret('CLIENT_ID_GITLAB')).secretValue;
export const getClientSecretGitLab = () => infisical.get('CLIENT_SECRET_GITLAB')!; export const getClientSecretAzure = async () => (await client.getSecret('CLIENT_SECRET_AZURE')).secretValue;
export const getClientSlugVercel = () => infisical.get('CLIENT_SLUG_VERCEL')!; export const getClientSecretHeroku = async () => (await client.getSecret('CLIENT_SECRET_HEROKU')).secretValue;
export const getPostHogHost = () => infisical.get('POSTHOG_HOST')! || 'https://app.posthog.com'; export const getClientSecretVercel = async () => (await client.getSecret('CLIENT_SECRET_VERCEL')).secretValue;
export const getPostHogProjectApiKey = () => infisical.get('POSTHOG_PROJECT_API_KEY')! || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; export const getClientSecretNetlify = async () => (await client.getSecret('CLIENT_SECRET_NETLIFY')).secretValue;
export const getSentryDSN = () => infisical.get('SENTRY_DSN')!; export const getClientSecretGitHub = async () => (await client.getSecret('CLIENT_SECRET_GITHUB')).secretValue;
export const getSessionSecret = () => infisical.get('SESSION_SECRET')!; export const getClientSecretGitLab = async () => (await client.getSecret('CLIENT_SECRET_GITLAB')).secretValue;
export const getSiteURL = () => infisical.get('SITE_URL')!; export const getClientSlugVercel = async () => (await client.getSecret('CLIENT_SLUG_VERCEL')).secretValue;
export const getSmtpHost = () => infisical.get('SMTP_HOST')!; export const getPostHogHost = async () => (await client.getSecret('POSTHOG_HOST')).secretValue || 'https://app.posthog.com';
export const getSmtpSecure = () => infisical.get('SMTP_SECURE')! === 'true' || false; export const getPostHogProjectApiKey = async () => (await client.getSecret('POSTHOG_PROJECT_API_KEY')).secretValue || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
export const getSmtpPort = () => parseInt(infisical.get('SMTP_PORT')!) || 587; export const getSentryDSN = async () => (await client.getSecret('SENTRY_DSN')).secretValue;
export const getSmtpUsername = () => infisical.get('SMTP_USERNAME')!; export const getSessionSecret = async () => (await client.getSecret('SESSION_SECRET')).secretValue;
export const getSmtpPassword = () => infisical.get('SMTP_PASSWORD')!; export const getSiteURL = async () => (await client.getSecret('SITE_URL')).secretValue;
export const getSmtpFromAddress = () => infisical.get('SMTP_FROM_ADDRESS')!; export const getSmtpHost = async () => (await client.getSecret('SMTP_HOST')).secretValue;
export const getSmtpFromName = () => infisical.get('SMTP_FROM_NAME')! || 'Infisical'; export const getSmtpSecure = async () => (await client.getSecret('SMTP_SECURE')).secretValue === 'true' || false;
export const getStripeProductStarter = () => infisical.get('STRIPE_PRODUCT_STARTER')!; export const getSmtpPort = async () => parseInt((await client.getSecret('SMTP_PORT')).secretValue) || 587;
export const getStripeProductPro = () => infisical.get('STRIPE_PRODUCT_PRO')!; export const getSmtpUsername = async () => (await client.getSecret('SMTP_USERNAME')).secretValue;
export const getStripeProductTeam = () => infisical.get('STRIPE_PRODUCT_TEAM')!; export const getSmtpPassword = async () => (await client.getSecret('SMTP_PASSWORD')).secretValue;
export const getStripePublishableKey = () => infisical.get('STRIPE_PUBLISHABLE_KEY')!; export const getSmtpFromAddress = async () => (await client.getSecret('SMTP_FROM_ADDRESS')).secretValue;
export const getStripeSecretKey = () => infisical.get('STRIPE_SECRET_KEY')!; export const getSmtpFromName = async () => (await client.getSecret('SMTP_FROM_NAME')).secretValue || 'Infisical';
export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!; export const getStripeProductStarter = async () => (await client.getSecret('STRIPE_PRODUCT_STARTER')).secretValue;
export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; export const getStripeProductPro = async () => (await client.getSecret('STRIPE_PRODUCT_PRO')).secretValue;
export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!; export const getStripeProductTeam = async () => (await client.getSecret('STRIPE_PRODUCT_TEAM')).secretValue;
export const getSmtpConfigured = () => infisical.get('SMTP_HOST') == '' || infisical.get('SMTP_HOST') == undefined ? false : true export const getStripePublishableKey = async () => (await client.getSecret('STRIPE_PUBLISHABLE_KEY')).secretValue;
export const getHttpsEnabled = () => { export const getStripeSecretKey = async () => (await client.getSecret('STRIPE_SECRET_KEY')).secretValue;
if (getNodeEnv() != "production") { export const getStripeWebhookSecret = async () => (await client.getSecret('STRIPE_WEBHOOK_SECRET')).secretValue;
export const getTelemetryEnabled = async () => (await client.getSecret('TELEMETRY_ENABLED')).secretValue !== 'false' && true;
export const getLoopsApiKey = async () => (await client.getSecret('LOOPS_API_KEY')).secretValue;
export const getSmtpConfigured = async () => (await client.getSecret('SMTP_HOST')).secretValue == '' || (await client.getSecret('SMTP_HOST')).secretValue == undefined ? false : true
export const getHttpsEnabled = async () => {
if ((await getNodeEnv()) != "production") {
// no https for anything other than prod // no https for anything other than prod
return false return false
} }
if (infisical.get('HTTPS_ENABLED') == undefined || infisical.get('HTTPS_ENABLED') == "") { if ((await client.getSecret('HTTPS_ENABLED')).secretValue == undefined || (await client.getSecret('HTTPS_ENABLED')).secretValue == "") {
// default when no value present // default when no value present
return true return true
} }
return infisical.get('HTTPS_ENABLED') === 'true' && true return (await client.getSecret('HTTPS_ENABLED')).secretValue === 'true' && true
} }
+5 -5
View File
@@ -126,7 +126,7 @@ export const login2 = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: '/', path: '/',
sameSite: 'strict', sameSite: 'strict',
secure: getHttpsEnabled() secure: await getHttpsEnabled()
}); });
const loginAction = await EELogService.createAction({ const loginAction = await EELogService.createAction({
@@ -182,7 +182,7 @@ export const logout = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: '/', path: '/',
sameSite: 'strict', sameSite: 'strict',
secure: getHttpsEnabled() as boolean secure: (await getHttpsEnabled()) as boolean
}); });
const logoutAction = await EELogService.createAction({ const logoutAction = await EELogService.createAction({
@@ -237,7 +237,7 @@ export const getNewToken = async (req: Request, res: Response) => {
} }
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(refreshToken, getJwtRefreshSecret()) jwt.verify(refreshToken, await getJwtRefreshSecret())
); );
const user = await User.findOne({ const user = await User.findOne({
@@ -252,8 +252,8 @@ export const getNewToken = async (req: Request, res: Response) => {
payload: { payload: {
userId: decodedToken.userId userId: decodedToken.userId
}, },
expiresIn: getJwtAuthLifetime(), expiresIn: await getJwtAuthLifetime(),
secret: getJwtAuthSecret() secret: await getJwtAuthSecret()
}); });
return res.status(200).send({ return res.status(200).send({
@@ -44,7 +44,7 @@ export const getIntegrationAuth = async (req: Request, res: Response) => {
} }
export const getIntegrationOptions = async (req: Request, res: Response) => { export const getIntegrationOptions = async (req: Request, res: Response) => {
const INTEGRATION_OPTIONS = getIntegrationOptionsFunc(); const INTEGRATION_OPTIONS = await getIntegrationOptionsFunc();
return res.status(200).send({ return res.status(200).send({
integrationOptions: INTEGRATION_OPTIONS, integrationOptions: INTEGRATION_OPTIONS,
@@ -215,7 +215,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
inviterFirstName: req.user.firstName, inviterFirstName: req.user.firstName,
inviterEmail: req.user.email, inviterEmail: req.user.email,
workspaceName: req.membership.workspace.name, workspaceName: req.membership.workspace.name,
callback_url: getSiteURL() + '/login' callback_url: (await getSiteURL()) + '/login'
} }
}); });
} catch (err) { } catch (err) {
@@ -180,11 +180,11 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
organizationName: organization.name, organizationName: organization.name,
email: inviteeEmail, email: inviteeEmail,
token, token,
callback_url: getSiteURL() + '/signupinvite' callback_url: (await getSiteURL()) + '/signupinvite'
} }
}); });
if (!getSmtpConfigured()) { if (!(await getSmtpConfigured())) {
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}` completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}`
} }
} }
@@ -257,8 +257,8 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
payload: { payload: {
userId: user._id.toString() userId: user._id.toString()
}, },
expiresIn: getJwtSignupLifetime(), expiresIn: await getJwtSignupLifetime(),
secret: getJwtSignupSecret() secret: await getJwtSignupSecret()
}); });
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
@@ -317,7 +317,7 @@ export const createOrganizationPortalSession = async (
) => { ) => {
let session; let session;
try { try {
const stripe = new Stripe(getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: '2022-08-01'
}); });
@@ -333,13 +333,13 @@ export const createOrganizationPortalSession = async (
customer: req.membershipOrg.organization.customerId, customer: req.membershipOrg.organization.customerId,
mode: 'setup', mode: 'setup',
payment_method_types: ['card'], payment_method_types: ['card'],
success_url: getSiteURL() + '/dashboard', success_url: (await getSiteURL()) + '/dashboard',
cancel_url: getSiteURL() + '/dashboard' cancel_url: (await getSiteURL()) + '/dashboard'
}); });
} else { } else {
session = await stripe.billingPortal.sessions.create({ session = await stripe.billingPortal.sessions.create({
customer: req.membershipOrg.organization.customerId, customer: req.membershipOrg.organization.customerId,
return_url: getSiteURL() + '/dashboard' return_url: (await getSiteURL()) + '/dashboard'
}); });
} }
@@ -365,7 +365,7 @@ export const getOrganizationSubscriptions = async (
) => { ) => {
let subscriptions; let subscriptions;
try { try {
const stripe = new Stripe(getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: '2022-08-01'
}); });
@@ -44,7 +44,7 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
substitutions: { substitutions: {
email, email,
token, token,
callback_url: getSiteURL() + '/password-reset' callback_url: (await getSiteURL()) + '/password-reset'
} }
}); });
} catch (err) { } catch (err) {
@@ -91,8 +91,8 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
payload: { payload: {
userId: user._id.toString() userId: user._id.toString()
}, },
expiresIn: getJwtSignupLifetime(), expiresIn: await getJwtSignupLifetime(),
secret: getJwtSignupSecret() secret: await getJwtSignupSecret()
}); });
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
@@ -39,7 +39,7 @@ export const pushSecrets = async (req: Request, res: Response) => {
// upload (encrypted) secrets to workspace with id [workspaceId] // upload (encrypted) secrets to workspace with id [workspaceId]
try { try {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
let { secrets }: { secrets: PushSecret[] } = req.body; let { secrets }: { secrets: PushSecret[] } = req.body;
const { keys, environment, channel } = req.body; const { keys, environment, channel } = req.body;
const { workspaceId } = req.params; const { workspaceId } = req.params;
@@ -114,7 +114,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
let secrets; let secrets;
let key; let key;
try { try {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string; const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string; const channel: string = req.query.channel as string;
const { workspaceId } = req.params; const { workspaceId } = req.params;
@@ -183,7 +183,7 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
let secrets; let secrets;
let key; let key;
try { try {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string; const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string; const channel: string = req.query.channel as string;
const { workspaceId } = req.params; const { workspaceId } = req.params;
@@ -0,0 +1,89 @@
import { Request, Response } from 'express';
import { Secret } from '../../models';
import Folder from '../../models/folder';
import { BadRequestError } from '../../utils/errors';
import { ROOT_FOLDER_PATH, getFolderPath, getParentPath, normalizePath, validateFolderName } from '../../utils/folder';
import { ADMIN, MEMBER } from '../../variables';
import { validateMembership } from '../../helpers/membership';
// TODO
// verify workspace id/environment
export const createFolder = async (req: Request, res: Response) => {
const { workspaceId, environment, folderName, parentFolderId } = req.body
if (!validateFolderName(folderName)) {
throw BadRequestError({ message: "Folder name cannot contain spaces. Only underscore and dashes" })
}
if (parentFolderId) {
const parentFolder = await Folder.find({ environment: environment, workspace: workspaceId, id: parentFolderId });
if (!parentFolder) {
throw BadRequestError({ message: "The parent folder doesn't exist" })
}
}
let completePath = await getFolderPath(parentFolderId)
if (completePath == ROOT_FOLDER_PATH) {
completePath = ""
}
const currentFolderPath = completePath + "/" + folderName // construct new path with current folder to be created
const normalizedCurrentPath = normalizePath(currentFolderPath)
const normalizedParentPath = getParentPath(normalizedCurrentPath)
const existingFolder = await Folder.findOne({
name: folderName,
workspace: workspaceId,
environment: environment,
parent: parentFolderId,
path: normalizedCurrentPath
});
if (existingFolder) {
return res.json(existingFolder)
}
const newFolder = new Folder({
name: folderName,
workspace: workspaceId,
environment: environment,
parent: parentFolderId,
path: normalizedCurrentPath,
parentPath: normalizedParentPath
});
await newFolder.save();
return res.json(newFolder)
}
export const deleteFolder = async (req: Request, res: Response) => {
const { folderId } = req.params
const queue: any[] = [folderId];
const folder = await Folder.findById(folderId);
if (!folder) {
throw BadRequestError({ message: "The folder doesn't exist" })
}
// check that user is a member of the workspace
await validateMembership({
userId: req.user._id.toString(),
workspaceId: folder.workspace as any,
acceptedRoles: [ADMIN, MEMBER]
});
while (queue.length > 0) {
const currentFolderId = queue.shift();
const childFolders = await Folder.find({ parent: currentFolderId });
for (const childFolder of childFolders) {
queue.push(childFolder._id);
}
await Secret.deleteMany({ folder: currentFolderId });
await Folder.deleteOne({ _id: currentFolderId });
}
res.send()
}
@@ -61,7 +61,7 @@ export const createServiceToken = async (req: Request, res: Response) => {
workspaceId workspaceId
}, },
expiresIn: expiresIn, expiresIn: expiresIn,
secret: getJwtServiceSecret() secret: await getJwtServiceSecret()
}); });
} catch (err) { } catch (err) {
return res.status(400).send({ return res.status(400).send({
@@ -21,7 +21,7 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
try { try {
email = req.body.email; email = req.body.email;
if (getInviteOnlySignup()) { if (await getInviteOnlySignup()) {
// Only one user can create an account without being invited. The rest need to be invited in order to make an account // Only one user can create an account without being invited. The rest need to be invited in order to make an account
const userCount = await User.countDocuments({}) const userCount = await User.countDocuments({})
if (userCount != 0) { if (userCount != 0) {
@@ -75,7 +75,7 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
} }
// verify email // verify email
if (getSmtpConfigured()) { if (await getSmtpConfigured()) {
await checkEmailVerification({ await checkEmailVerification({
email, email,
code code
@@ -93,8 +93,8 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
payload: { payload: {
userId: user._id.toString() userId: user._id.toString()
}, },
expiresIn: getJwtSignupLifetime(), expiresIn: await getJwtSignupLifetime(),
secret: getJwtSignupSecret() secret: await getJwtSignupSecret()
}); });
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
@@ -13,7 +13,7 @@ export const handleWebhook = async (req: Request, res: Response) => {
let event; let event;
try { try {
// check request for valid stripe signature // check request for valid stripe signature
const stripe = new Stripe(getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: '2022-08-01'
}); });
@@ -21,7 +21,7 @@ export const handleWebhook = async (req: Request, res: Response) => {
event = stripe.webhooks.constructEvent( event = stripe.webhooks.constructEvent(
req.body, req.body,
sig, sig,
getStripeWebhookSecret() await getStripeWebhookSecret()
); );
} catch (err) { } catch (err) {
Sentry.setUser({ email: req.user.email }); Sentry.setUser({ email: req.user.email });
@@ -43,7 +43,7 @@ export const createAPIKeyData = async (req: Request, res: Response) => {
const { name, expiresIn } = req.body; const { name, expiresIn } = req.body;
const secret = crypto.randomBytes(16).toString('hex'); const secret = crypto.randomBytes(16).toString('hex');
const secretHash = await bcrypt.hash(secret, getSaltRounds()); const secretHash = await bcrypt.hash(secret, await getSaltRounds());
const expiresAt = new Date(); const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
+4 -4
View File
@@ -124,8 +124,8 @@ export const login2 = async (req: Request, res: Response) => {
payload: { payload: {
userId: user._id.toString() userId: user._id.toString()
}, },
expiresIn: getJwtMfaLifetime(), expiresIn: await getJwtMfaLifetime(),
secret: getJwtMfaSecret() secret: await getJwtMfaSecret()
}); });
const code = await TokenService.createToken({ const code = await TokenService.createToken({
@@ -163,7 +163,7 @@ export const login2 = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: '/', path: '/',
sameSite: 'strict', sameSite: 'strict',
secure: getHttpsEnabled() secure: await getHttpsEnabled()
}); });
// case: user does not have MFA enablgged // case: user does not have MFA enablgged
@@ -302,7 +302,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: '/', path: '/',
sameSite: 'strict', sameSite: 'strict',
secure: getHttpsEnabled() secure: await getHttpsEnabled()
}); });
interface VerifyMfaTokenRes { interface VerifyMfaTokenRes {
@@ -17,7 +17,7 @@ import { AccountNotFoundError } from '../../utils/errors';
* @param res * @param res
*/ */
export const createSecret = async (req: Request, res: Response) => { export const createSecret = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const secretToCreate: CreateSecretRequestBody = req.body.secret; const secretToCreate: CreateSecretRequestBody = req.body.secret;
const { workspaceId, environment } = req.params const { workspaceId, environment } = req.params
const sanitizedSecret: SanitizedSecretForCreate = { const sanitizedSecret: SanitizedSecretForCreate = {
@@ -70,7 +70,7 @@ export const createSecret = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const createSecrets = async (req: Request, res: Response) => { export const createSecrets = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets; const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
const { workspaceId, environment } = req.params const { workspaceId, environment } = req.params
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = [] const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = []
@@ -132,7 +132,7 @@ export const createSecrets = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const deleteSecrets = async (req: Request, res: Response) => { export const deleteSecrets = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params const { workspaceId, environmentName } = req.params
const secretIdsToDelete: string[] = req.body.secretIds const secretIdsToDelete: string[] = req.body.secretIds
@@ -186,7 +186,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const deleteSecret = async (req: Request, res: Response) => { export const deleteSecret = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
await Secret.findByIdAndDelete(req._secret._id) await Secret.findByIdAndDelete(req._secret._id)
if (postHogClient) { if (postHogClient) {
@@ -215,7 +215,7 @@ export const deleteSecret = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const updateSecrets = async (req: Request, res: Response) => { export const updateSecrets = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params const { workspaceId, environmentName } = req.params
const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets; const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets;
const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()) const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then())
@@ -283,7 +283,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const updateSecret = async (req: Request, res: Response) => { export const updateSecret = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params const { workspaceId, environmentName } = req.params
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret; const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
@@ -337,7 +337,7 @@ export const updateSecret = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getSecrets = async (req: Request, res: Response) => { export const getSecrets = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const { environment } = req.query; const { environment } = req.query;
const { workspaceId } = req.params; const { workspaceId } = req.params;
+45 -12
View File
@@ -25,6 +25,8 @@ import {
BatchSecretRequest, BatchSecretRequest,
BatchSecret BatchSecret
} from '../../types/secret'; } from '../../types/secret';
import { getFolderPath, getFoldersInDirectory, normalizePath } from '../../utils/folder';
import { ROOT_FOLDER_PATH } from '../../utils/folder';
/** /**
* Peform a batch of any specified CUD secret operations * Peform a batch of any specified CUD secret operations
@@ -35,7 +37,7 @@ import {
export const batchSecrets = async (req: Request, res: Response) => { export const batchSecrets = async (req: Request, res: Response) => {
const channel = getChannelFromUserAgent(req.headers['user-agent']); const channel = getChannelFromUserAgent(req.headers['user-agent']);
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const { const {
workspaceId, workspaceId,
@@ -51,13 +53,18 @@ export const batchSecrets = async (req: Request, res: Response) => {
const updateSecrets: BatchSecret[] = []; const updateSecrets: BatchSecret[] = [];
const deleteSecrets: Types.ObjectId[] = []; const deleteSecrets: Types.ObjectId[] = [];
const actions: IAction[] = []; const actions: IAction[] = [];
// get secret blind index salt // get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({ const salt = await SecretService.getSecretBlindIndexSalt({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId)
}); });
for await (const request of requests) { for await (const request of requests) {
const folderId = request.secret.folderId
// TODO: need to auth folder
const fullFolderPath = await getFolderPath(folderId)
let secretBlindIndex = ''; let secretBlindIndex = '';
switch (request.method) { switch (request.method) {
case 'POST': case 'POST':
@@ -72,19 +79,23 @@ export const batchSecrets = async (req: Request, res: Response) => {
user: request.secret.type === SECRET_PERSONAL ? req.user : undefined, user: request.secret.type === SECRET_PERSONAL ? req.user : undefined,
environment, environment,
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
path: fullFolderPath,
folder: folderId,
secretBlindIndex secretBlindIndex
}); });
break; break;
case 'PATCH': case 'PATCH':
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({ secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
secretName: request.secret.secretName, secretName: request.secret.secretName,
salt salt,
}); });
updateSecrets.push({ updateSecrets.push({
...request.secret, ...request.secret,
_id: new Types.ObjectId(request.secret._id), _id: new Types.ObjectId(request.secret._id),
secretBlindIndex secretBlindIndex,
folder: folderId,
path: fullFolderPath,
}); });
break; break;
case 'DELETE': case 'DELETE':
@@ -437,9 +448,9 @@ export const createSecrets = async (req: Request, res: Response) => {
}); });
}) })
); );
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map((insertedSecret) => insertedSecret.toObject()); const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map((insertedSecret) => insertedSecret.toObject());
setTimeout(async () => { setTimeout(async () => {
// trigger event - push secrets // trigger event - push secrets
await EventService.handleEvent({ await EventService.handleEvent({
@@ -508,7 +519,7 @@ export const createSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId)
}); });
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets added', event: 'secrets added',
@@ -578,9 +589,11 @@ export const getSecrets = async (req: Request, res: Response) => {
} }
*/ */
const { tagSlugs } = req.query; const { tagSlugs, secretsPath } = req.query;
const workspaceId = req.query.workspaceId as string; const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string; const environment = req.query.environment as string;
const normalizedPath = normalizePath(secretsPath as string)
const folders = await getFoldersInDirectory(workspaceId as string, environment as string, normalizedPath)
// secrets to return // secrets to return
let secrets: ISecret[] = []; let secrets: ISecret[] = [];
@@ -613,6 +626,12 @@ export const getSecrets = async (req: Request, res: Response) => {
] ]
} }
if (normalizedPath == ROOT_FOLDER_PATH) {
secretQuery.path = { $in: [ROOT_FOLDER_PATH, null, undefined] }
} else if (normalizedPath) {
secretQuery.path = normalizedPath
}
if (tagIds.length > 0) { if (tagIds.length > 0) {
secretQuery.tags = { $in: tagIds }; secretQuery.tags = { $in: tagIds };
} }
@@ -638,6 +657,13 @@ export const getSecrets = async (req: Request, res: Response) => {
] ]
} }
// TODO: check if user can query for given path
if (normalizedPath == ROOT_FOLDER_PATH) {
secretQuery.path = { $in: [ROOT_FOLDER_PATH, null, undefined] }
} else if (normalizedPath) {
secretQuery.path = normalizedPath
}
if (tagIds.length > 0) { if (tagIds.length > 0) {
secretQuery.tags = { $in: tagIds }; secretQuery.tags = { $in: tagIds };
} }
@@ -655,6 +681,12 @@ export const getSecrets = async (req: Request, res: Response) => {
user: { $exists: false } // shared secrets only from workspace user: { $exists: false } // shared secrets only from workspace
} }
if (normalizedPath == ROOT_FOLDER_PATH) {
secretQuery.path = { $in: [ROOT_FOLDER_PATH, null, undefined] }
} else if (normalizedPath) {
secretQuery.path = normalizedPath
}
if (tagIds.length > 0) { if (tagIds.length > 0) {
secretQuery.tags = { $in: tagIds }; secretQuery.tags = { $in: tagIds };
} }
@@ -683,7 +715,7 @@ export const getSecrets = async (req: Request, res: Response) => {
ipAddress: req.ip ipAddress: req.ip
}); });
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets pulled', event: 'secrets pulled',
@@ -701,7 +733,8 @@ export const getSecrets = async (req: Request, res: Response) => {
} }
return res.status(200).send({ return res.status(200).send({
secrets secrets,
folders
}); });
} }
@@ -905,7 +938,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key) workspaceId: new Types.ObjectId(key)
}) })
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets modified', event: 'secrets modified',
@@ -1039,7 +1072,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key) workspaceId: new Types.ObjectId(key)
}); });
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets deleted', event: 'secrets deleted',
@@ -72,7 +72,7 @@ export const createServiceAccount = async (req: Request, res: Response) => {
} }
const secret = crypto.randomBytes(16).toString('base64'); const secret = crypto.randomBytes(16).toString('base64');
const secretHash = await bcrypt.hash(secret, getSaltRounds()); const secretHash = await bcrypt.hash(secret, await getSaltRounds());
// create service account // create service account
const serviceAccount = await new ServiceAccount({ const serviceAccount = await new ServiceAccount({
@@ -84,7 +84,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
} = req.body; } = req.body;
const secret = crypto.randomBytes(16).toString('hex'); const secret = crypto.randomBytes(16).toString('hex');
const secretHash = await bcrypt.hash(secret, getSaltRounds()); const secretHash = await bcrypt.hash(secret, await getSaltRounds());
let expiresAt; let expiresAt;
if (expiresIn) { if (expiresIn) {
@@ -108,7 +108,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
token = tokens.token; token = tokens.token;
// sending a welcome email to new users // sending a welcome email to new users
if (getLoopsApiKey()) { if (await getLoopsApiKey()) {
await request.post("https://app.loops.so/api/v1/events/send", { await request.post("https://app.loops.so/api/v1/events/send", {
"email": email, "email": email,
"eventName": "Sign Up", "eventName": "Sign Up",
@@ -117,7 +117,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
}, { }, {
headers: { headers: {
"Accept": "application/json", "Accept": "application/json",
"Authorization": "Bearer " + getLoopsApiKey() "Authorization": "Bearer " + (await getLoopsApiKey())
}, },
}); });
} }
@@ -127,7 +127,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: '/', path: '/',
sameSite: 'strict', sameSite: 'strict',
secure: getHttpsEnabled() secure: await getHttpsEnabled()
}); });
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
@@ -232,7 +232,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: '/', path: '/',
sameSite: 'strict', sameSite: 'strict',
secure: getHttpsEnabled() secure: await getHttpsEnabled()
}); });
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
@@ -48,7 +48,7 @@ interface V2PushSecret {
export const pushWorkspaceSecrets = async (req: Request, res: Response) => { export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
// upload (encrypted) secrets to workspace with id [workspaceId] // upload (encrypted) secrets to workspace with id [workspaceId]
try { try {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
let { secrets }: { secrets: V2PushSecret[] } = req.body; let { secrets }: { secrets: V2PushSecret[] } = req.body;
const { keys, environment, channel } = req.body; const { keys, environment, channel } = req.body;
const { workspaceId } = req.params; const { workspaceId } = req.params;
@@ -123,7 +123,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
export const pullSecrets = async (req: Request, res: Response) => { export const pullSecrets = async (req: Request, res: Response) => {
let secrets; let secrets;
try { try {
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string; const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string; const channel: string = req.query.channel as string;
const { workspaceId } = req.params; const { workspaceId } = req.params;
@@ -12,7 +12,7 @@ import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config';
export const handleWebhook = async (req: Request, res: Response) => { export const handleWebhook = async (req: Request, res: Response) => {
let event; let event;
try { try {
const stripe = new Stripe(getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: '2022-08-01'
}); });
@@ -21,7 +21,7 @@ export const handleWebhook = async (req: Request, res: Response) => {
event = stripe.webhooks.constructEvent( event = stripe.webhooks.constructEvent(
req.body, req.body,
sig, sig,
getStripeWebhookSecret() await getStripeWebhookSecret()
); );
} catch (err) { } catch (err) {
Sentry.setUser({ email: req.user.email }); Sentry.setUser({ email: req.user.email });
+5 -5
View File
@@ -104,7 +104,7 @@ const getAuthUserPayload = async ({
authTokenValue: string; authTokenValue: string;
}) => { }) => {
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(authTokenValue, getJwtAuthSecret()) jwt.verify(authTokenValue, await getJwtAuthSecret())
); );
const user = await User.findOne({ const user = await User.findOne({
@@ -263,16 +263,16 @@ const issueAuthTokens = async ({ userId }: { userId: string }) => {
payload: { payload: {
userId userId
}, },
expiresIn: getJwtAuthLifetime(), expiresIn: await getJwtAuthLifetime(),
secret: getJwtAuthSecret() secret: await getJwtAuthSecret()
}); });
const refreshToken = createToken({ const refreshToken = createToken({
payload: { payload: {
userId userId
}, },
expiresIn: getJwtRefreshLifetime(), expiresIn: await getJwtRefreshLifetime(),
secret: getJwtRefreshSecret() secret: await getJwtRefreshSecret()
}); });
return { return {
+2 -2
View File
@@ -119,7 +119,7 @@ const createBot = async ({
const { publicKey, privateKey } = generateKeyPair(); const { publicKey, privateKey } = generateKeyPair();
const { ciphertext, iv, tag } = encryptSymmetric({ const { ciphertext, iv, tag } = encryptSymmetric({
plaintext: privateKey, plaintext: privateKey,
key: getEncryptionKey() key: await getEncryptionKey()
}); });
bot = await new Bot({ bot = await new Bot({
@@ -216,7 +216,7 @@ const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) => {
ciphertext: bot.encryptedPrivateKey, ciphertext: bot.encryptedPrivateKey,
iv: bot.iv, iv: bot.iv,
tag: bot.tag, tag: bot.tag,
key: getEncryptionKey() key: await getEncryptionKey()
}); });
key = decryptAsymmetric({ key = decryptAsymmetric({
+2 -2
View File
@@ -20,12 +20,12 @@ const initDatabaseHelper = async ({
// allow empty strings to pass the required validator // allow empty strings to pass the required validator
mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string'); mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string');
getLogger("database").info("Database connection established"); (await getLogger("database")).info("Database connection established");
await EESecretService.initSecretVersioning(); await EESecretService.initSecretVersioning();
await SecretService.initSecretBlindIndexDataHelper(); await SecretService.initSecretBlindIndexDataHelper();
} catch (err) { } catch (err) {
getLogger("database").error(`Unable to establish Database connection due to the error.\n${err}`); (await getLogger("database")).error(`Unable to establish Database connection due to the error.\n${err}`);
} }
return mongoose.connection; return mongoose.connection;
+2 -2
View File
@@ -25,7 +25,7 @@ const sendMail = async ({
recipients: string[]; recipients: string[];
substitutions: any; substitutions: any;
}) => { }) => {
if (getSmtpConfigured()) { if (await getSmtpConfigured()) {
try { try {
const html = fs.readFileSync( const html = fs.readFileSync(
path.resolve(__dirname, '../templates/' + template), path.resolve(__dirname, '../templates/' + template),
@@ -35,7 +35,7 @@ const sendMail = async ({
const htmlToSend = temp(substitutions); const htmlToSend = temp(substitutions);
await smtpTransporter.sendMail({ await smtpTransporter.sendMail({
from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`, from: `"${await getSmtpFromName()}" <${await getSmtpFromAddress()}>`,
to: recipients.join(', '), to: recipients.join(', '),
subject: subjectLine, subject: subjectLine,
html: htmlToSend html: htmlToSend
+7 -7
View File
@@ -123,11 +123,11 @@ const createOrganization = async ({
let organization; let organization;
try { try {
// register stripe account // register stripe account
const stripe = new Stripe(getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: '2022-08-01'
}); });
if (getStripeSecretKey()) { if (await getStripeSecretKey()) {
const customer = await stripe.customers.create({ const customer = await stripe.customers.create({
email, email,
description: name description: name
@@ -177,14 +177,14 @@ const initSubscriptionOrg = async ({
if (organization) { if (organization) {
if (organization.customerId) { if (organization.customerId) {
// initialize starter subscription with quantity of 0 // initialize starter subscription with quantity of 0
const stripe = new Stripe(getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: '2022-08-01'
}); });
const productToPriceMap = { const productToPriceMap = {
starter: getStripeProductStarter(), starter: await getStripeProductStarter(),
team: getStripeProductTeam(), team: await getStripeProductTeam(),
pro: getStripeProductPro() pro: await getStripeProductPro()
}; };
stripeSubscription = await stripe.subscriptions.create({ stripeSubscription = await stripe.subscriptions.create({
@@ -239,7 +239,7 @@ const updateSubscriptionOrgQuantity = async ({
status: ACCEPTED status: ACCEPTED
}); });
const stripe = new Stripe(getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: '2022-08-01'
}); });
+28 -26
View File
@@ -233,27 +233,29 @@ const initSecretBlindIndexDataHelper = async () => {
} }
}); });
const secretBlindIndexDataToInsert = workspaceIdsToBlindIndex.map((workspaceToBlindIndex) => { const secretBlindIndexDataToInsert = await Promise.all(
const salt = crypto.randomBytes(16).toString('base64'); workspaceIdsToBlindIndex.map(async (workspaceToBlindIndex) => {
const salt = crypto.randomBytes(16).toString('base64');
const { const {
ciphertext: encryptedSaltCiphertext, ciphertext: encryptedSaltCiphertext,
iv: saltIV, iv: saltIV,
tag: saltTag tag: saltTag
} = encryptSymmetric({ } = encryptSymmetric({
plaintext: salt, plaintext: salt,
key: getEncryptionKey() key: await getEncryptionKey()
}); });
const secretBlindIndexData = new SecretBlindIndexData({ const secretBlindIndexData = new SecretBlindIndexData({
workspace: workspaceToBlindIndex, workspace: workspaceToBlindIndex,
encryptedSaltCiphertext, encryptedSaltCiphertext,
saltIV, saltIV,
saltTag saltTag
})
return secretBlindIndexData;
}) })
);
return secretBlindIndexData;
});
if (secretBlindIndexDataToInsert.length > 0) { if (secretBlindIndexDataToInsert.length > 0) {
await SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert); await SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert);
@@ -280,7 +282,7 @@ const createSecretBlindIndexDataHelper = async ({
tag: saltTag tag: saltTag
} = encryptSymmetric({ } = encryptSymmetric({
plaintext: salt, plaintext: salt,
key: getEncryptionKey() key: await getEncryptionKey()
}); });
const secretBlindIndexData = await new SecretBlindIndexData({ const secretBlindIndexData = await new SecretBlindIndexData({
@@ -316,7 +318,7 @@ const getSecretBlindIndexSaltHelper = async ({
ciphertext: secretBlindIndexData.encryptedSaltCiphertext, ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
iv: secretBlindIndexData.saltIV, iv: secretBlindIndexData.saltIV,
tag: secretBlindIndexData.saltTag, tag: secretBlindIndexData.saltTag,
key: getEncryptionKey() key: await getEncryptionKey()
}); });
return salt; return salt;
@@ -378,7 +380,7 @@ const generateSecretBlindIndexHelper = async ({
ciphertext: secretBlindIndexData.encryptedSaltCiphertext, ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
iv: secretBlindIndexData.saltIV, iv: secretBlindIndexData.saltIV,
tag: secretBlindIndexData.saltTag, tag: secretBlindIndexData.saltTag,
key: getEncryptionKey() key: await getEncryptionKey()
}); });
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({ const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
@@ -508,7 +510,7 @@ const createSecretHelper = async ({
workspaceId workspaceId
}); });
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
@@ -578,7 +580,7 @@ const getSecretsHelper = async ({
ipAddress: authData.authIP ipAddress: authData.authIP
}); });
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
@@ -660,7 +662,7 @@ const getSecretHelper = async ({
ipAddress: authData.authIP ipAddress: authData.authIP
}); });
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
@@ -798,7 +800,7 @@ const updateSecretHelper = async ({
workspaceId workspaceId
}); });
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
@@ -905,7 +907,7 @@ const deleteSecretHelper = async ({
workspaceId workspaceId
}); });
const postHogClient = TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
+1 -1
View File
@@ -84,7 +84,7 @@ const createTokenHelper = async ({
const query: TokenDataQuery = { type }; const query: TokenDataQuery = { type };
const update: TokenDataUpdate = { const update: TokenDataUpdate = {
type, type,
tokenHash: await bcrypt.hash(token, getSaltRounds()), tokenHash: await bcrypt.hash(token, await getSaltRounds()),
expiresAt expiresAt
} }
-1
View File
@@ -28,7 +28,6 @@ import {
AUTH_MODE_SERVICE_TOKEN, AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY AUTH_MODE_API_KEY
} from '../variables'; } from '../variables';
import { getEncryptionKey } from '../config';
import { encryptSymmetric } from '../utils/crypto'; import { encryptSymmetric } from '../utils/crypto';
import { SecretService } from '../services'; import { SecretService } from '../services';
+13 -18
View File
@@ -1,6 +1,5 @@
import dotenv from 'dotenv'; import dotenv from 'dotenv';
dotenv.config(); dotenv.config();
import infisical from 'infisical-node';
import express from 'express'; import express from 'express';
import helmet from 'helmet'; import helmet from 'helmet';
import cors from 'cors'; import cors from 'cors';
@@ -45,7 +44,8 @@ import {
password as v1PasswordRouter, password as v1PasswordRouter,
stripe as v1StripeRouter, stripe as v1StripeRouter,
integration as v1IntegrationRouter, integration as v1IntegrationRouter,
integrationAuth as v1IntegrationAuthRouter integrationAuth as v1IntegrationAuthRouter,
secretsFolder as v1SecretsFolder
} from './routes/v1'; } from './routes/v1';
import { import {
signup as v2SignupRouter, signup as v2SignupRouter,
@@ -80,22 +80,16 @@ import {
} from './config'; } from './config';
const main = async () => { const main = async () => {
if (process.env.INFISICAL_TOKEN != "" || process.env.INFISICAL_TOKEN != undefined) {
await infisical.connect({
token: process.env.INFISICAL_TOKEN!
});
}
TelemetryService.logTelemetryMessage(); TelemetryService.logTelemetryMessage();
setTransporter(initSmtp()); setTransporter(await initSmtp());
await DatabaseService.initDatabase(getMongoURL()); await DatabaseService.initDatabase(await getMongoURL());
if (getNodeEnv() !== 'test') { if ((await getNodeEnv()) !== 'test') {
Sentry.init({ Sentry.init({
dsn: getSentryDSN(), dsn: await getSentryDSN(),
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
debug: getNodeEnv() === 'production' ? false : true, debug: await getNodeEnv() === 'production' ? false : true,
environment: getNodeEnv() environment: await getNodeEnv()
}); });
} }
@@ -107,7 +101,7 @@ const main = async () => {
app.use( app.use(
cors({ cors({
credentials: true, credentials: true,
origin: getSiteURL() origin: await getSiteURL()
}) })
); );
@@ -118,7 +112,7 @@ const main = async () => {
saveUninitialized: false, // don't create session until something stored saveUninitialized: false, // don't create session until something stored
})); }));
if (getNodeEnv() === 'production') { if ((await getNodeEnv()) === 'production') {
// enable app-wide rate-limiting + helmet security // enable app-wide rate-limiting + helmet security
// in production // in production
app.disable('x-powered-by'); app.disable('x-powered-by');
@@ -150,6 +144,7 @@ const main = async () => {
app.use('/api/v1/stripe', v1StripeRouter); app.use('/api/v1/stripe', v1StripeRouter);
app.use('/api/v1/integration', v1IntegrationRouter); app.use('/api/v1/integration', v1IntegrationRouter);
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter); app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
app.use('/api/v1/folder', v1SecretsFolder)
// v2 routes (improvements) // v2 routes (improvements)
app.use('/api/v2/signup', v2SignupRouter); app.use('/api/v2/signup', v2SignupRouter);
@@ -184,8 +179,8 @@ const main = async () => {
app.use(requestErrorHandler) app.use(requestErrorHandler)
const server = app.listen(getPort(), () => { const server = app.listen(await getPort(), async () => {
getLogger("backend-main").info(`Server started listening at port ${getPort()}`) (await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`)
}); });
await createTestUserForDevelopment(); await createTestUserForDevelopment();
+16 -16
View File
@@ -159,9 +159,9 @@ const exchangeCodeAzure = async ({
grant_type: 'authorization_code', grant_type: 'authorization_code',
code: code, code: code,
scope: 'https://vault.azure.net/.default openid offline_access', scope: 'https://vault.azure.net/.default openid offline_access',
client_id: getClientIdAzure(), client_id: await getClientIdAzure(),
client_secret: getClientSecretAzure(), client_secret: await getClientSecretAzure(),
redirect_uri: `${getSiteURL()}/integrations/azure-key-vault/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/azure-key-vault/oauth2/callback`
} as any) } as any)
)).data; )).data;
@@ -204,7 +204,7 @@ const exchangeCodeHeroku = async ({
new URLSearchParams({ new URLSearchParams({
grant_type: 'authorization_code', grant_type: 'authorization_code',
code: code, code: code,
client_secret: getClientSecretHeroku() client_secret: await getClientSecretHeroku()
} as any) } as any)
)).data; )).data;
@@ -242,9 +242,9 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => {
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
code: code, code: code,
client_id: getClientIdVercel(), client_id: await getClientIdVercel(),
client_secret: getClientSecretVercel(), client_secret: await getClientSecretVercel(),
redirect_uri: `${getSiteURL()}/integrations/vercel/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/vercel/oauth2/callback`
} as any) } as any)
) )
).data; ).data;
@@ -282,9 +282,9 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
new URLSearchParams({ new URLSearchParams({
grant_type: 'authorization_code', grant_type: 'authorization_code',
code: code, code: code,
client_id: getClientIdNetlify(), client_id: await getClientIdNetlify(),
client_secret: getClientSecretNetlify(), client_secret: await getClientSecretNetlify(),
redirect_uri: `${getSiteURL()}/integrations/netlify/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/netlify/oauth2/callback`
} as any) } as any)
) )
).data; ).data;
@@ -333,10 +333,10 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
res = ( res = (
await request.get(INTEGRATION_GITHUB_TOKEN_URL, { await request.get(INTEGRATION_GITHUB_TOKEN_URL, {
params: { params: {
client_id: getClientIdGitHub(), client_id: await getClientIdGitHub(),
client_secret: getClientSecretGitHub(), client_secret: await getClientSecretGitHub(),
code: code, code: code,
redirect_uri: `${getSiteURL()}/integrations/github/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/github/oauth2/callback`
}, },
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
@@ -379,9 +379,9 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
new URLSearchParams({ new URLSearchParams({
grant_type: 'authorization_code', grant_type: 'authorization_code',
code: code, code: code,
client_id: getClientIdGitLab(), client_id: await getClientIdGitLab(),
client_secret: getClientSecretGitLab(), client_secret: await getClientSecretGitLab(),
redirect_uri: `${getSiteURL()}/integrations/gitlab/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback`
} as any), } as any),
{ {
headers: { headers: {
+6 -6
View File
@@ -133,11 +133,11 @@ const exchangeRefreshAzure = async ({
const { data }: { data: RefreshTokenAzureResponse } = await request.post( const { data }: { data: RefreshTokenAzureResponse } = await request.post(
INTEGRATION_AZURE_TOKEN_URL, INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
client_id: getClientIdAzure(), client_id: await getClientIdAzure(),
scope: 'openid offline_access', scope: 'openid offline_access',
refresh_token: refreshToken, refresh_token: refreshToken,
grant_type: 'refresh_token', grant_type: 'refresh_token',
client_secret: getClientSecretAzure() client_secret: await getClientSecretAzure()
} as any) } as any)
); );
@@ -180,7 +180,7 @@ const exchangeRefreshHeroku = async ({
new URLSearchParams({ new URLSearchParams({
grant_type: 'refresh_token', grant_type: 'refresh_token',
refresh_token: refreshToken, refresh_token: refreshToken,
client_secret: getClientSecretHeroku() client_secret: await getClientSecretHeroku()
} as any) } as any)
); );
@@ -223,9 +223,9 @@ const exchangeRefreshGitLab = async ({
new URLSearchParams({ new URLSearchParams({
grant_type: 'refresh_token', grant_type: 'refresh_token',
refresh_token: refreshToken, refresh_token: refreshToken,
client_id: getClientIdGitLab, client_id: await getClientIdGitLab,
client_secret: getClientSecretGitLab(), client_secret: await getClientSecretGitLab(),
redirect_uri: `${getSiteURL()}/integrations/gitlab/oauth2/callback` redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback`
} as any), } as any),
{ {
headers: { headers: {
@@ -5,9 +5,9 @@ import { getLogger } from "../utils/logger";
import RequestError, { LogLevel } from "../utils/requestError"; import RequestError, { LogLevel } from "../utils/requestError";
import { getNodeEnv } from '../config'; import { getNodeEnv } from '../config';
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => { export const requestErrorHandler: ErrorRequestHandler = async (error: RequestError | Error, req, res, next) => {
if (res.headersSent) return next(); if (res.headersSent) return next();
if (getNodeEnv() !== "production") { if ((await getNodeEnv()) !== "production") {
/* eslint-disable no-console */ /* eslint-disable no-console */
console.log(error) console.log(error)
/* eslint-enable no-console */ /* eslint-enable no-console */
@@ -15,8 +15,8 @@ export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | E
//TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError //TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError
if (!(error instanceof RequestError)) { if (!(error instanceof RequestError)) {
error = InternalServerError({ context: { exception: error.message }, stack: error.stack }) error = InternalServerError({ context: { exception: error.message }, stack: error.stack });
getLogger('backend-main').log((<RequestError>error).levelName.toLowerCase(), (<RequestError>error).message) (await getLogger('backend-main')).log((<RequestError>error).levelName.toLowerCase(), (<RequestError>error).message)
} }
//* Set Sentry user identification if req.user is populated //* Set Sentry user identification if req.user is populated
+1 -1
View File
@@ -26,7 +26,7 @@ const requireMfaAuth = async (
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, getJwtMfaSecret()) jwt.verify(AUTH_TOKEN_VALUE, await getJwtMfaSecret())
); );
const user = await User.findOne({ const user = await User.findOne({
@@ -33,7 +33,7 @@ const requireServiceTokenAuth = async (
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, getJwtServiceSecret()) jwt.verify(AUTH_TOKEN_VALUE, await getJwtServiceSecret())
); );
const serviceToken = await ServiceToken.findOne({ const serviceToken = await ServiceToken.findOne({
+1 -1
View File
@@ -27,7 +27,7 @@ const requireSignupAuth = async (
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, getJwtSignupSecret()) jwt.verify(AUTH_TOKEN_VALUE, await getJwtSignupSecret())
); );
const user = await User.findOne({ const user = await User.findOne({
+36
View File
@@ -0,0 +1,36 @@
import { Schema, Types, model } from 'mongoose';
const folderSchema = new Schema({
name: {
type: String,
required: true,
},
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true,
},
environment: {
type: String,
required: true,
},
parent: {
type: Schema.Types.ObjectId,
ref: 'Folder',
required: false, // optional for root folders
},
path: {
type: String,
required: true
},
parentPath: {
type: String,
required: true,
},
}, {
timestamps: true
});
const Folder = model('Folder', folderSchema);
export default Folder;
+15 -1
View File
@@ -3,6 +3,7 @@ import {
SECRET_SHARED, SECRET_SHARED,
SECRET_PERSONAL, SECRET_PERSONAL,
} from '../variables'; } from '../variables';
import { ROOT_FOLDER_PATH } from '../utils/folder';
export interface ISecret { export interface ISecret {
_id: Types.ObjectId; _id: Types.ObjectId;
@@ -25,6 +26,8 @@ export interface ISecret {
secretCommentTag?: string; secretCommentTag?: string;
secretCommentHash?: string; secretCommentHash?: string;
tags?: string[]; tags?: string[];
path?: string;
folder?: Types.ObjectId;
} }
const secretSchema = new Schema<ISecret>( const secretSchema = new Schema<ISecret>(
@@ -107,7 +110,18 @@ const secretSchema = new Schema<ISecret>(
secretCommentHash: { secretCommentHash: {
type: String, type: String,
required: false required: false
} },
// the full path to the secret in relation to folders
path: {
type: String,
required: false,
default: ROOT_FOLDER_PATH
},
folder: {
type: Schema.Types.ObjectId,
ref: 'Folder',
required: false,
},
}, },
{ {
timestamps: true timestamps: true
+2 -2
View File
@@ -5,11 +5,11 @@ const router = express.Router();
router.get( router.get(
'/status', '/status',
(req: Request, res: Response) => { async (req: Request, res: Response) => {
res.status(200).json({ res.status(200).json({
date: new Date(), date: new Date(),
message: 'Ok', message: 'Ok',
emailConfigured: getSmtpConfigured() emailConfigured: await getSmtpConfigured()
}) })
} }
); );
+3 -1
View File
@@ -15,6 +15,7 @@ import password from './password';
import stripe from './stripe'; import stripe from './stripe';
import integration from './integration'; import integration from './integration';
import integrationAuth from './integrationAuth'; import integrationAuth from './integrationAuth';
import secretsFolder from './secretsFolder'
export { export {
signup, signup,
@@ -33,5 +34,6 @@ export {
password, password,
stripe, stripe,
integration, integration,
integrationAuth integrationAuth,
secretsFolder
}; };
+40
View File
@@ -0,0 +1,40 @@
import express, { Request, Response } from 'express';
const router = express.Router();
import {
requireAuth,
requireWorkspaceAuth,
validateRequest
} from '../../middleware';
import { body, param } from 'express-validator';
import { createFolder, deleteFolder } from '../../controllers/v1/secretsFolderController';
import { ADMIN, MEMBER } from '../../variables';
router.post(
'/',
requireAuth({
acceptedAuthModes: ['jwt']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'body'
}),
body('workspaceId').exists(),
body('environment').exists(),
body('folderName').exists(),
body('parentFolderId'),
validateRequest,
createFolder
);
router.delete(
'/:folderId',
requireAuth({
acceptedAuthModes: ['jwt']
}),
param('folderId').exists(),
validateRequest,
deleteFolder
);
export default router;
-2
View File
@@ -1,5 +1,3 @@
import mongoose from 'mongoose';
import { getLogger } from '../utils/logger';
import { import {
initDatabaseHelper, initDatabaseHelper,
closeDatabaseHelper closeDatabaseHelper
+7 -7
View File
@@ -24,9 +24,9 @@ class Telemetry {
/** /**
* Logs telemetry enable/disable notice. * Logs telemetry enable/disable notice.
*/ */
static logTelemetryMessage = () => { static logTelemetryMessage = async () => {
if(!getTelemetryEnabled()){ if(!(await getTelemetryEnabled())){
getLogger("backend-main").info([ (await getLogger("backend-main")).info([
"", "",
"To improve, Infisical collects telemetry data about general usage.", "To improve, Infisical collects telemetry data about general usage.",
"This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.", "This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.",
@@ -39,12 +39,12 @@ class Telemetry {
* Return an instance of the PostHog client initialized. * Return an instance of the PostHog client initialized.
* @returns * @returns
*/ */
static getPostHogClient = () => { static getPostHogClient = async () => {
let postHogClient: any; let postHogClient: any;
if (getNodeEnv() === 'production' && getTelemetryEnabled()) { if ((await getNodeEnv()) === 'production' && (await getTelemetryEnabled())) {
// case: enable opt-out telemetry in production // case: enable opt-out telemetry in production
postHogClient = new PostHog(getPostHogProjectApiKey(), { postHogClient = new PostHog(await getPostHogProjectApiKey(), {
host: getPostHogHost() host: await getPostHogHost()
}); });
} }
+2 -2
View File
@@ -3,8 +3,8 @@ import { createTerminus } from '@godaddy/terminus';
import { getLogger } from '../utils/logger'; import { getLogger } from '../utils/logger';
export const setUpHealthEndpoint = <T>(server: T) => { export const setUpHealthEndpoint = <T>(server: T) => {
const onSignal = () => { const onSignal = async () => {
getLogger('backend-main').info('Server is starting clean-up'); (await getLogger('backend-main')).info('Server is starting clean-up');
return Promise.all([ return Promise.all([
new Promise((resolve) => { new Promise((resolve) => {
if (mongoose.connection && mongoose.connection.readyState == 1) { if (mongoose.connection && mongoose.connection.readyState == 1) {
+11 -11
View File
@@ -15,21 +15,21 @@ import {
getSmtpPort getSmtpPort
} from '../config'; } from '../config';
export const initSmtp = () => { export const initSmtp = async () => {
const mailOpts: SMTPConnection.Options = { const mailOpts: SMTPConnection.Options = {
host: getSmtpHost(), host: await getSmtpHost(),
port: getSmtpPort() port: await getSmtpPort()
}; };
if (getSmtpUsername() && getSmtpPassword()) { if ((await getSmtpUsername()) && (await getSmtpPassword())) {
mailOpts.auth = { mailOpts.auth = {
user: getSmtpUsername(), user: await getSmtpUsername(),
pass: getSmtpPassword() pass: await getSmtpPassword()
}; };
} }
if (getSmtpSecure() ? getSmtpSecure() : false) { if ((await getSmtpSecure()) ? (await getSmtpSecure()) : false) {
switch (getSmtpHost()) { switch (await getSmtpHost()) {
case SMTP_HOST_SENDGRID: case SMTP_HOST_SENDGRID:
mailOpts.requireTLS = true; mailOpts.requireTLS = true;
break; break;
@@ -52,7 +52,7 @@ export const initSmtp = () => {
} }
break; break;
default: default:
if (getSmtpHost().includes('amazonaws.com')) { if ((await getSmtpHost()).includes('amazonaws.com')) {
mailOpts.tls = { mailOpts.tls = {
ciphers: 'TLSv1.2' ciphers: 'TLSv1.2'
} }
@@ -70,10 +70,10 @@ export const initSmtp = () => {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureMessage('SMTP - Successfully connected'); Sentry.captureMessage('SMTP - Successfully connected');
}) })
.catch((err) => { .catch(async (err) => {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException( Sentry.captureException(
`SMTP - Failed to connect to ${getSmtpHost()}:${getSmtpPort()} \n\t${err}` `SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()} \n\t${err}`
); );
}); });
+1 -1
View File
@@ -19,7 +19,7 @@ export const testWorkspaceKeyId = "63cf48f0225e6955acec5eff"
export const plainTextWorkspaceKey = "543fef8224813a46230b0a50a46c5fb2" export const plainTextWorkspaceKey = "543fef8224813a46230b0a50a46c5fb2"
export const createTestUserForDevelopment = async () => { export const createTestUserForDevelopment = async () => {
if (getNodeEnv() === "development" || getNodeEnv() === "test") { if ((await getNodeEnv()) === "development" || (await getNodeEnv()) === "test") {
const testUser = { const testUser = {
_id: testUserId, _id: testUserId,
email: testUserEmail, email: testUserEmail,
+87
View File
@@ -0,0 +1,87 @@
import Folder from "../models/folder";
export const ROOT_FOLDER_PATH = "/"
export const getFolderPath = async (folderId: string) => {
let currentFolder = await Folder.findById(folderId);
const pathSegments = [];
while (currentFolder) {
pathSegments.unshift(currentFolder.name);
currentFolder = currentFolder.parent ? await Folder.findById(currentFolder.parent) : null;
}
return '/' + pathSegments.join('/');
};
/**
Returns the folder ID associated with the specified secret path in the given workspace and environment.
@param workspaceId - The ID of the workspace to search in.
@param environment - The environment to search in.
@param secretPath - The secret path to search for.
@returns The folder ID associated with the specified secret path, or undefined if the path is at the root folder level.
@throws Error if the specified secret path is not found.
*/
export const getFolderIdFromPath = async (workspaceId: string, environment: string, secretPath: string) => {
const secretPathParts = secretPath.split("/").filter(path => path != "")
if (secretPathParts.length <= 1) {
return undefined // root folder, so no folder id
}
const folderId = await Folder.find({ path: secretPath, workspace: workspaceId, environment: environment })
if (!folderId) {
throw Error("Secret path not found")
}
return folderId
}
/**
* Cleans up a path by removing empty parts, duplicate slashes,
* and ensuring it starts with ROOT_FOLDER_PATH.
* @param path - The input path to clean up.
* @returns The cleaned-up path string.
*/
export const normalizePath = (path: string) => {
if (path == undefined || path == "" || path == ROOT_FOLDER_PATH) {
return ROOT_FOLDER_PATH
}
const pathParts = path.split("/").filter(part => part != "")
const cleanPathString = ROOT_FOLDER_PATH + pathParts.join("/")
return cleanPathString
}
export const getFoldersInDirectory = async (workspaceId: string, environment: string, pathString: string) => {
const normalizedPath = normalizePath(pathString)
const foldersInDirectory = await Folder.find({
workspace: workspaceId,
environment: environment,
parentPath: normalizedPath,
});
return foldersInDirectory;
}
/**
* Returns the parent path of the given path.
* @param path - The input path.
* @returns The parent path string.
*/
export const getParentPath = (path: string) => {
const normalizedPath = normalizePath(path);
const folderParts = normalizedPath.split('/').filter(part => part !== '');
let folderParent = ROOT_FOLDER_PATH;
if (folderParts.length > 1) {
folderParent = ROOT_FOLDER_PATH + folderParts.slice(0, folderParts.length - 1).join('/');
}
return folderParent;
}
export const validateFolderName = (folderName: string) => {
const validNameRegex = /^[a-zA-Z0-9-_]+$/;
return validNameRegex.test(folderName);
}
+10 -12
View File
@@ -12,7 +12,7 @@ const logFormat = (prefix: string) => combine(
printf((info) => `${info.timestamp} ${info.label} ${info.level}: ${info.message}`) printf((info) => `${info.timestamp} ${info.label} ${info.level}: ${info.message}`)
); );
const createLoggerWithLabel = (level: string, label: string) => { const createLoggerWithLabel = async (level: string, label: string) => {
const _level = level.toLowerCase() || 'info' const _level = level.toLowerCase() || 'info'
//* Always add Console output to transports //* Always add Console output to transports
const _transports: any[] = [ const _transports: any[] = [
@@ -25,10 +25,10 @@ const createLoggerWithLabel = (level: string, label: string) => {
}) })
] ]
//* Add LokiTransport if it's enabled //* Add LokiTransport if it's enabled
if(getLokiHost() !== undefined){ if((await getLokiHost()) !== undefined){
_transports.push( _transports.push(
new LokiTransport({ new LokiTransport({
host: getLokiHost(), host: await getLokiHost(),
handleExceptions: true, handleExceptions: true,
handleRejections: true, handleRejections: true,
batching: true, batching: true,
@@ -40,7 +40,7 @@ const createLoggerWithLabel = (level: string, label: string) => {
labels: { labels: {
app: process.env.npm_package_name, app: process.env.npm_package_name,
version: process.env.npm_package_version, version: process.env.npm_package_version,
environment: getNodeEnv() environment: await getNodeEnv()
}, },
onConnectionError: (err: Error)=> console.error('Connection error while connecting to Loki Server.\n', err) onConnectionError: (err: Error)=> console.error('Connection error while connecting to Loki Server.\n', err)
}) })
@@ -58,12 +58,10 @@ const createLoggerWithLabel = (level: string, label: string) => {
}); });
} }
const DEFAULT_LOGGERS = { export const getLogger = async (loggerName: 'backend-main' | 'database') => {
"backend-main": createLoggerWithLabel('info', '[IFSC:backend-main]'), const logger = {
"database": createLoggerWithLabel('info', '[IFSC:database]'), "backend-main": await createLoggerWithLabel('info', '[IFSC:backend-main]'),
} "database": await createLoggerWithLabel('info', '[IFSC:database]'),
type LoggerNames = keyof typeof DEFAULT_LOGGERS }
return logger[loggerName]
export const getLogger = (loggerName: LoggerNames) => {
return DEFAULT_LOGGERS[loggerName]
} }
+2 -2
View File
@@ -81,13 +81,13 @@ export default class RequestError extends Error{
return obj return obj
} }
public format(req: Request){ public async format(req: Request){
let _context = Object.assign({ let _context = Object.assign({
stacktrace: this.stacktrace stacktrace: this.stacktrace
}, this.context) }, this.context)
//* Omit sensitive information from context that can leak internal workings of this program if user is not developer //* Omit sensitive information from context that can leak internal workings of this program if user is not developer
if(!getVerboseErrorOutput()){ if(!(await getVerboseErrorOutput())){
_context = this._omit(_context, [ _context = this._omit(_context, [
'stacktrace', 'stacktrace',
'exception', 'exception',
+7 -7
View File
@@ -61,7 +61,7 @@ const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com"; const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com'; const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com';
const getIntegrationOptions = () => { const getIntegrationOptions = async () => {
const INTEGRATION_OPTIONS = [ const INTEGRATION_OPTIONS = [
{ {
name: 'Heroku', name: 'Heroku',
@@ -69,7 +69,7 @@ const getIntegrationOptions = () => {
image: 'Heroku.png', image: 'Heroku.png',
isAvailable: true, isAvailable: true,
type: 'oauth', type: 'oauth',
clientId: getClientIdHeroku(), clientId: await getClientIdHeroku(),
docsLink: '' docsLink: ''
}, },
{ {
@@ -79,7 +79,7 @@ const getIntegrationOptions = () => {
isAvailable: true, isAvailable: true,
type: 'oauth', type: 'oauth',
clientId: '', clientId: '',
clientSlug: getClientSlugVercel(), clientSlug: await getClientSlugVercel(),
docsLink: '' docsLink: ''
}, },
{ {
@@ -88,7 +88,7 @@ const getIntegrationOptions = () => {
image: 'Netlify.png', image: 'Netlify.png',
isAvailable: true, isAvailable: true,
type: 'oauth', type: 'oauth',
clientId: getClientIdNetlify(), clientId: await getClientIdNetlify(),
docsLink: '' docsLink: ''
}, },
{ {
@@ -97,7 +97,7 @@ const getIntegrationOptions = () => {
image: 'GitHub.png', image: 'GitHub.png',
isAvailable: true, isAvailable: true,
type: 'oauth', type: 'oauth',
clientId: getClientIdGitHub(), clientId: await getClientIdGitHub(),
docsLink: '' docsLink: ''
}, },
{ {
@@ -151,7 +151,7 @@ const getIntegrationOptions = () => {
image: 'Microsoft Azure.png', image: 'Microsoft Azure.png',
isAvailable: true, isAvailable: true,
type: 'oauth', type: 'oauth',
clientId: getClientIdAzure(), clientId: await getClientIdAzure(),
docsLink: '' docsLink: ''
}, },
{ {
@@ -169,7 +169,7 @@ const getIntegrationOptions = () => {
image: 'GitLab.png', image: 'GitLab.png',
isAvailable: true, isAvailable: true,
type: 'custom', type: 'custom',
clientId: getClientIdGitLab(), clientId: await getClientIdGitLab(),
docsLink: '' docsLink: ''
}, },
{ {
+19 -17
View File
@@ -64,7 +64,9 @@ These examples demonstrate how to store and fetch environment variables from [In
### Initialize the Infisical client ### Initialize the Infisical client
```js ```js
await infisical.connect({ import InfisicalClient from "infisical-node";
const client = new InfisicalClient({
token: "your_infisical_token", token: "your_infisical_token",
}); });
``` ```
@@ -72,31 +74,31 @@ These examples demonstrate how to store and fetch environment variables from [In
### Get a value ### Get a value
```js ```js
const value = infisical.get("SOME_KEY"); const value = await client.getSecret("SOME_KEY");
``` ```
### Example with Express ### Example with Express
```js ```js
const express = require("express"); import InfisicalClient from "infisical-node";
const port = 3000; import express from "express";
const infisical = require("infisical-node"); const app = express();
const PORT = 3000;
const main = async () => { const client = InfisicalClient({
await infisical.connect({ token: "st.xxx.xxx",
token: "st.xxx.xxx", });
});
// your application logic // your application logic
app.get("/", (req, res) => { app.get("/", async (req, res) => {
res.send(`Howdy, ${infisical.get("NAME")}!`); const name = await client.getSecret("NAME");
}); res.send(`Hello! My name is: ${name.secretValue}`);
});
app.listen(port, async () => { app.listen(PORT, async () => {
console.log(`App listening on port ${port}`); console.log(`App listening on port ${port}`);
}); });
};
``` ```
<Warning> <Warning>
+156 -117
View File
@@ -2,7 +2,39 @@
title: "Node" title: "Node"
--- ---
If you're working with Node.js, the official [infisical-node](https://github.com/Infisical/infisical-node) package is the easiest way to fetch secrets for your application. If you're working with Node.js, the official [infisical-node](https://github.com/Infisical/infisical-node) package is the easiest way to fetch and work with secrets for your application.
## Basic Usage
```js
import InfisicalClient from "infisical-node";
import express from "express";
const app = express();
const PORT = 3000;
const client = new InfisicalClient({
token: "YOUR_INFISICAL_TOKEN"
});
app.get("/", async (req, res) => {
// access value
const name = await client.getSecret("NAME");
res.send(`Hello! My name is: ${name.secretValue}`);
});
app.listen(PORT, async () => {
// initialize client
console.log(`App listening on port ${port}`);
});
```
This example demonstrates how to use the Infisical SDK with an Express application. The application retrieves a secret named "NAME" and responds to requests with a greeting that includes the secret value.
<Warning>
We do not recommend hardcoding your [Infisical
Token](/getting-started/dashboard/token). Setting it as an environment
variable would be best.
</Warning>
## Installation ## Installation
@@ -12,143 +44,150 @@ Run `npm` to add `infisical-node` to your project.
npm install infisical-node --save npm install infisical-node --save
``` ```
## Initialization ## Configuration
Set up the Infisical client asynchronously as early as possible in your application by importing and initializing the global instance with `infisical.connect(options)`. Import the SDK and create a client instance with your Infisical token.
This methods fetches back all the secrets in the project and environment accessible by the token passed in `options`.
### infisical.connect(options)
Updates the global instance of the Infisical client with a connection to an Infisical project and fetches back secrets if supplied with an [Infisical Token](/getting-started/dashboard/token).
<ResponseField name="options" type="object">
<Expandable title="properties">
<ResponseField name="token" type="string">
An [Infisical Token](/getting-started/dashboard/token) scoped to a project
and environment
</ResponseField>
<ResponseField
name="siteURL"
type="string"
default="https://app.infisical.com"
>
Your self-hosted absolute site URL including the protocol (e.g.
`https://app.infisical.com`)
</ResponseField>
<ResponseField name="debug" type="boolean" default="false">
Whether or not debug mode is on
</ResponseField>
<ResponseField name="attachToProcessEnv" type="boolean" default="false">
Whether or not to attach fetched secrets to `process.env`
</ResponseField>
</Expandable>
</ResponseField>
### infisical.createConnection(options)
Returns a local instance of the Infisical client with a connection to an Infisical project and fetches back secrets if supplied with an [Infisical Token](/getting-started/dashboard/token).
This method is useful if you wish to connect to two or more Infisical projects within your app.
<ResponseField name="options" type="object">
<Expandable title="properties">
<ResponseField name="token" type="string">
An [Infisical Token](/getting-started/dashboard/token) scoped to a project
and environment
</ResponseField>
<ResponseField
name="siteURL"
type="string"
default="https://app.infisical.com"
>
Your self-hosted absolute site URL including the protocol (e.g.
`https://app.infisical.com`)
</ResponseField>
<ResponseField name="debug" type="boolean" default="false">
Whether or not debug mode is on
</ResponseField>
</Expandable>
</ResponseField>
<Tabs> <Tabs>
<Tab title="ES6"> <Tab title="ES6">
```js ```js
import infisical from "infisical-node"; import InfisicalClient from "infisical-node";
const client = new InfisicalClient({
token: "your_infisical_token"
});
const main = async () => { // your app logic
await infisical.connect({
token: "your_infisical_token",
});
// your app logic
}
main();
``` ```
</Tab> </Tab>
<Tab title="ES5"> <Tab title="ES5">
```js ```js
const infisical = require("infisical-node"); const InfisicalClient = require("infisical-node");
infisical.connect({ const client = new InfisicalClient({
token: "your_infisical_token" token: "your_infisical_token"
}) });
.then(() => {
// your application logic // your app logic
})
.catch(err => {
console.error('Error: ', err);
})
```` ````
</Tab> </Tab>
</Tabs> </Tabs>
## Usage <ResponseField name="options" type="object">
<Expandable title="properties">
To get the value of a secret, use `infisical.get(key)`. <ResponseField name="token" type="string">
An [Infisical Token](/getting-started/dashboard/token) scoped to a project
### infisical.get(key) and environment
</ResponseField>
Return the value of the secret with the specified `key`. Note that the Infisical client falls back to `process.env` if `token` is `undefined` during the <ResponseField
initialization step or if a value for the secret is not found in the fetched secrets. name="siteURL"
type="string"
<ResponseField name="key" type="string" required> default="https://app.infisical.com"
The key of the secret >
Your self-hosted absolute site URL including the protocol (e.g.
`https://app.infisical.com`)
</ResponseField>
<ResponseField name="cacheTTL" type="number" default="300">
Time-to-live (in seconds) for refreshing cached secrets. Default: `300`.
</ResponseField>
<ResponseField name="debug" type="boolean" default="false">
Whether or not debug mode is on
</ResponseField>
</Expandable>
</ResponseField> </ResponseField>
```js ## Caching
const value = infisical.get("SOME_KEY");
```
## Example with Express The SDK caches every secret and updates it periodically based on the provided `cacheTTL`. For example, if `cacheTTL` of `300` is provided, then a secret will be refetched 5 minutes after the first fetch; if the fetch fails, the cached secret is returned.
## Working with Secrets
### infisical.getSecret(secretName, options)
```js ```js
const express = require("express"); const secret = await infisical.getSecret("API_KEY");
const port = 3000; const value = secret.secretValue; // get its value
const infisical = require("infisical-node");
const main = async () => {
await infisical.connect({
token: "st.xxx.xxx",
});
// your application logic
app.get("/", (req, res) => {
res.send(`Howdy, ${infisical.get("NAME")}!`);
});
app.listen(port, async () => {
console.log(`App listening on port ${port}`);
});
};
``` ```
<Warning> Retrieve a secret from Infisical.
We do not recommend hardcoding your [Infisical
Token](/getting-started/dashboard/token). Setting it as an environment By default, `getSecret()` fetches and returns a personal secret. If not found, it returns a shared secret, or tries to retrieve the value from `process.env`. If a secret is fetched, `getSecret()` caches it to reduce excessive calls and re-fetches periodically based on the `cacheTTL` option (default is `300` seconds) when initializing the client — for more information, see the caching section.
variable would be best.
</Warning>
<ResponseField name="secretName" type="string" required>
The key of the secret to retrieve
</ResponseField>
<ResponseField name="options" type="object">
<Expandable title="properties">
<ResponseField name="type" type="string">
"personal" (default) or "shared".
</ResponseField>
</Expandable>
</ResponseField>
### infisical.createSecret(secretName, secretValue, options)
```js
const newApiKey = await infisical.createSecret("API_KEY", "FOO");
```
Create a new secret in Infisical.
<ResponseField name="secretName" type="string" required>
The key of the secret to create
</ResponseField>
<ResponseField name="secretName" type="string" required>
The value of the secret to create
</ResponseField>
<ResponseField name="options" type="object">
<Expandable title="properties">
<ResponseField name="type" type="string">
"shared" (default) or "personal". A personal secret can only be created if a shared secret with the same name exists.
</ResponseField>
</Expandable>
</ResponseField>
### infisical.updateSecret(secretName, secretValue, options)
```js
const updatedApiKey = await infisical.updateSecret("API_KEY", "BAR");
```
Update an existing secret in Infisical.
<ResponseField name="secretName" type="string" required>
The key of the secret to update
</ResponseField>
<ResponseField name="secretName" type="string" required>
The new value of the secret
</ResponseField>
<ResponseField name="options" type="object">
<Expandable title="properties">
<ResponseField name="type" type="string">
"shared" (default) or "personal".
</ResponseField>
</Expandable>
</ResponseField>
### infisical.deleteSecret(secretName, options)
```js
const deletedSecret = await infisical.deleteSecret("API_KEY");
```
Delete a secret in Infisical.
<ResponseField name="secretName" type="string" required>
The key of the secret to delete
</ResponseField>
<ResponseField name="options" type="object">
<Expandable title="properties">
<ResponseField name="type" type="string">
"shared" (default) or "personal". Note that deleting a shared secret also deletes all associated personal secrets.
</ResponseField>
</Expandable>
</ResponseField>
-2
View File
@@ -803,8 +803,6 @@ export default function Dashboard() {
isReadDenied: false isReadDenied: false
}; };
console.log(124, envSlug, selectedWorkspaceEnv)
if (selectedWorkspaceEnv) { if (selectedWorkspaceEnv) {
if (snapshotData) setSelectedSnapshotEnv(selectedWorkspaceEnv); if (snapshotData) setSelectedSnapshotEnv(selectedWorkspaceEnv);
else setSelectedEnv(selectedWorkspaceEnv); else setSelectedEnv(selectedWorkspaceEnv);
@@ -125,7 +125,7 @@ export const DashboardEnvOverview = ({onEnvChange}: {onEnvChange: any;}) => {
if (isSecretsLoading || isEnvListLoading) { if (isSecretsLoading || isEnvListLoading) {
return ( return (
<div className="container mx-auto flex h-full w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]"> <div className="container mx-auto flex h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
<img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" /> <img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
</div> </div>
); );
@@ -234,14 +234,14 @@ export const DashboardEnvOverview = ({onEnvChange}: {onEnvChange: any;}) => {
</Button> </Button>
</div> */} </div> */}
</div> </div>
<div className="group min-w-full flex flex-row items-center mt-4"> <div className="group flex flex-row items-center mt-4 min-w-[60.3rem]">
<div className="w-10 h-10 px-4 flex items-center justify-center border-none"><div className='text-center w-10 text-xs text-transparent'>0</div></div> <div className="w-10 h-10 px-4 flex items-center justify-center border-none"><div className='text-center w-10 text-xs text-transparent'>0</div></div>
<div className="flex flex-row justify-between items-center min-w-[200px] lg:min-w-[220px] xl:min-w-[250px]"> <div className="flex flex-row justify-between items-center min-w-[200px] lg:min-w-[220px] xl:min-w-[250px]">
<span className="text-transparent">0</span> <span className="text-transparent">0</span>
<button type="button" className='mr-2 text-transparent'>1</button> <button type="button" className='mr-2 text-transparent'>1</button>
</div> </div>
{userAvailableEnvs?.map(env => { {userAvailableEnvs?.map(env => {
return <div key={`button-${env.slug}`} className="flex flex-row w-full justify-center h-10 items-center border-none mb-1 mx-2 min-w-[10rem]"> return <div key={`button-${env.slug}`} className="flex flex-row w-full justify-center h-10 items-center border-none mb-1 mx-2 min-w-[11rem]">
<Button <Button
onClick={() => onEnvChange(env.slug)} onClick={() => onEnvChange(env.slug)}
// router.push(`${router.asPath }?env=${env.slug}`) // router.push(`${router.asPath }?env=${env.slug}`)
@@ -28,7 +28,7 @@ const DashboardInput = ({ isOverridden, isSecretValueHidden, isReadOnly, secret,
ref.current.scrollLeft = e.currentTarget.scrollLeft; ref.current.scrollLeft = e.currentTarget.scrollLeft;
}; };
return <td key={`row-${secret?.key || ''}--`} className={`flex cursor-default flex-row w-full min-w-[11rem] justify-center h-10 items-center ${!(secret?.value || secret?.value === '') ? "bg-red-400/10" : "bg-mineshaft-900/30"}`}> return <td key={`row-${secret?.key || ''}--`} className={`flex cursor-default flex-row w-full justify-center h-10 items-center ${!(secret?.value || secret?.value === '') ? "bg-red-400/10" : "bg-mineshaft-900/30"}`}>
<div className="group relative whitespace-pre flex flex-col justify-center w-full cursor-default"> <div className="group relative whitespace-pre flex flex-col justify-center w-full cursor-default">
<input <input
// {...register(`secrets.${index}.valueOverride`)} // {...register(`secrets.${index}.valueOverride`)}