1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-31 22:09:57 +00:00

Compare commits

..

77 Commits

Author SHA1 Message Date
8893aec213 patch saml sso for rippling 2024-01-23 01:11:29 -05:00
c4cb8f8008 Merge pull request from Infisical/daniel/better-sdk-docs
(Docs): Improve SDK docs
2024-01-23 08:53:28 +04:00
046557c97f Add .NET 2024-01-23 08:47:29 +04:00
a15ba28c18 Update node guide to reflect new SDK 2024-01-23 08:47:20 +04:00
8386f4dcbd Update python guide to reflect new SDK 2024-01-23 08:47:11 +04:00
ada0fd9c5b Add new item for fallbacks 2024-01-23 08:47:01 +04:00
6376c29e49 update maintenace notice 2024-01-19 17:28:35 -05:00
829e906650 Merge pull request from Infisical/daniel/changelog-jan-2024
[JAN] Changelog points
2024-01-18 20:25:52 -05:00
b7cbb0f1a8 Update overview.mdx 2024-01-19 00:14:03 +04:00
a50ffbb59d Update overview.mdx 2024-01-19 00:13:57 +04:00
48eda0c684 Update overview.mdx 2024-01-15 15:55:37 +04:00
ed89413689 Merge pull request from Infisical/update-blog
Update blog notification
2024-01-12 13:52:11 -05:00
0c94f77a6d Merge pull request from akhilmhdh/feat/banner
feat: added main banner for migration
2024-01-12 10:48:21 -05:00
e6068826f8 update migration notification 2024-01-11 23:34:46 -08:00
cfa0a2044e Merge pull request from Infisical/add-project-config-flag-for-workspace
add project-config-dir flag for run command
2024-01-11 17:21:03 -05:00
134b503c28 remove log 2024-01-11 17:19:39 -05:00
efcbf1aa88 add project-config-dir flag for run command 2024-01-11 16:39:02 -05:00
284c18db07 Merge pull request from Infisical/daniel/sdk-seo-improvements
(Docs): SDK SEO Improvements & links to packages/repos
2024-01-11 10:53:08 -08:00
1410a44610 SEO Improvements and links to packages/repos 2024-01-11 22:34:11 +04:00
746ffb3840 feat: added main banner for migration 2024-01-11 23:09:57 +05:30
f9f12eafdf Update saml docs 2024-01-10 15:02:10 -08:00
11470a5a0e Merge pull request from Infisical/daniel/project-docs-secret-reminders
(Docs): Personal overrides and secret reminders awareness
2024-01-10 17:43:16 -05:00
9fe2190115 Update project.mdx 2024-01-11 02:40:37 +04:00
9e2bd31833 Merge pull request from Infisical/daniel/csharp-docs
(Docs): .NET SDK documentation & updates existing SDK docs
2024-01-10 16:28:40 -05:00
e88b0ad3c4 Update python.mdx 2024-01-11 01:25:10 +04:00
74644fd8bb Added cryptography docs and fixed formatting 2024-01-11 01:12:38 +04:00
2069ac1554 Added CSharp and removed unfinished SDK's 2024-01-11 01:12:26 +04:00
5a2516e0a7 Removed unsupported languages to remove clutter 2024-01-11 01:12:17 +04:00
b52bc3bed7 Added CSharp docs 2024-01-11 01:12:05 +04:00
4a153e5658 Merge pull request from akhilmhdh/fix/sec-interpolation-undefined
fix(secret-reference): fixed undefined if value not found
2024-01-10 09:59:09 -05:00
7324822be5 fix(secret-reference): fixed undefined if value not found 2024-01-10 11:45:46 +05:30
766f301aea patch agent config by env 2024-01-09 14:30:29 -05:00
8fbc930012 updated notification text 2024-01-08 13:51:40 -08:00
0e5190a920 added the december update blog 2024-01-08 13:47:58 -08:00
b815e3eb56 Merge pull request from Infisical/daniel/fix-sdk-contribution-image
(Fix): Image in SDK contribution guide not loading
2024-01-08 14:56:27 -05:00
31231cfcca Update developing.mdx 2024-01-08 23:30:10 +04:00
ee772e4a77 allow reading universal auth creds from env in agent 2024-01-07 17:00:42 -05:00
7bc29c5981 Merge pull request from Infisical/query-by-secret-version
Add version query param to GET secret raw and regular endpoints
2024-01-07 16:07:49 -05:00
e9a89930da Merge pull request from Infisical/multi-integration-auth
Enable new integration auth credential for each new integration
2024-01-07 14:49:04 -05:00
b85499859c Merge pull request from Infisical/identities-ipv6
Add IPv6 consideration to default universal auth IP allowlist
2024-01-07 16:37:06 +01:00
7f17194c0f Add IPv6 consideration to default identities IP allowlist 2024-01-07 16:32:25 +01:00
1e1ad450d2 Add version query param to GET secret endpoint 2024-01-07 14:25:33 +01:00
5287b322d8 Enable new integration auth for each new integration 2024-01-07 12:49:59 +01:00
45d96be1ff added base64 support for config and templates 2024-01-06 23:43:04 -05:00
12840bfdbd add exit after auth setting 2024-01-06 17:17:21 -05:00
fef5369738 Merge pull request from Infisical/identity-apis
Update various identities items
2024-01-06 17:11:01 +01:00
c94b7d63f6 Update various identities items 2024-01-06 17:04:44 +01:00
485ddc5c50 Merge pull request from Infisical/patch-railway
Fix client-side railway integration issue
2024-01-06 16:14:16 +01:00
edd9c66e49 Remove commented print statements 2024-01-06 16:11:22 +01:00
0a3b85534b Fix client-side railway integration issue 2024-01-06 16:09:15 +01:00
ec2cc5162e Merge pull request from Infisical/daniel/sdk-contribution-guide
Contribution guide refactor & SDK contribution guide
2024-01-05 20:26:17 -05:00
7ce472957c Fixed quality 2024-01-06 04:04:09 +04:00
8529e0da3d Update developing.mdx 2024-01-06 03:41:31 +04:00
e5a5433f10 Update developing.mdx 2024-01-06 03:00:14 +04:00
ee6e518ff8 Update link to contribution guide 2024-01-06 02:58:26 +04:00
15a7222505 Update mint.json 2024-01-06 02:58:16 +04:00
25d482cc62 Create sdk-flow.png 2024-01-06 02:58:12 +04:00
785a2bec6a Added SDK guide 2024-01-06 02:58:08 +04:00
449466f326 Restructure 2024-01-06 02:58:02 +04:00
4131e9c3f1 Added getting started section 2024-01-06 02:57:53 +04:00
310595256f Restructured existing guide 2024-01-06 02:57:21 +04:00
1737880e58 Merge pull request from Infisical/snyk-fix-b96b562a611b0789d0a73c522a261f22
[Snyk] Security upgrade probot from 12.3.1 to 12.3.3
2024-01-05 11:20:43 -05:00
b72483f5f2 Merge pull request from Emiliaaah/fix-agent-secret-path
fix(cli): secret-path directive for agent
2024-01-05 10:39:39 -05:00
ee14bda706 Merge pull request from rlaisqls/error-message-typos
Fix error message typos
2024-01-05 18:18:20 +04:00
e56463d52b fix(cli): secret-path directive for agent 2024-01-05 15:05:57 +01:00
ebd3d7c7c4 Merge pull request from Infisical/fix-vercel-preview-env
Fix: Vercel integration preview environment client side error
2024-01-04 10:18:25 -05:00
9ecbfe201b Update create.tsx 2024-01-04 17:42:31 +04:00
ba2a03897f update secret import create notif 2024-01-04 01:55:34 -05:00
304f14c0ed update service token create notif 2024-01-04 01:52:03 -05:00
51e5c25e16 update imports/service token crud 2024-01-04 00:55:03 -05:00
0f6490b1e7 move cli to bin folder 2024-01-03 20:17:34 -05:00
f894e48fcb remove unused import 2024-01-02 13:55:01 -05:00
37cfa22619 add back macos build 2024-01-02 13:47:15 -05:00
94557344b7 wrap cli into a docker image 2024-01-02 13:43:55 -05:00
d5063018eb Added identities, universal auth, agent to changelog 2024-01-02 10:05:43 +01:00
ade27ad072 Fix typos 2023-12-29 13:26:08 +09:00
c7b2489d0b fix: backend/package.json & backend/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-PROBOT-6129524
2023-12-17 14:48:29 +00:00
75 changed files with 1859 additions and 401 deletions
.goreleaser.yamlREADME.md
backend
cli
docs
frontend
public/images
src
components
hooks/api
integrationAuth
integrations
workspace
layouts
AdminLayout
AppLayout
pages
integrations
org/[id]/overview
views
IntegrationsPage
Org/MembersPage/components/OrgIdentityTab/components/IdentitySection
Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenSection
SecretMainPage/components/ActionBar
admin/SignUpPage

@ -108,7 +108,7 @@ brews:
zsh_completion.install "completions/infisical.zsh" => "_infisical"
fish_completion.install "completions/infisical.fish"
man1.install "manpages/infisical.1.gz"
- name: 'infisical@{{.Version}}'
- name: "infisical@{{.Version}}"
tap:
owner: Infisical
name: homebrew-get-cli
@ -186,12 +186,14 @@ aurs:
# man pages
install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz"
# dockers:
# - dockerfile: cli/docker/Dockerfile
# goos: linux
# goarch: amd64
# ids:
# - infisical
# image_templates:
# - "infisical/cli:{{ .Version }}"
# - "infisical/cli:latest"
dockers:
- dockerfile: docker/alpine
goos: linux
goarch: amd64
ids:
- all-other-builds
image_templates:
- "infisical/cli:{{ .Version }}"
- "infisical/cli:{{ .Major }}.{{ .Minor }}"
- "infisical/cli:{{ .Major }}"
- "infisical/cli:latest"

@ -129,7 +129,7 @@ Note that this security address should be used only for undisclosed vulnerabilit
## Contributing
Whether it's big or small, we love contributions. Check out our guide to see how to [get started](https://infisical.com/docs/contributing/overview).
Whether it's big or small, we love contributions. Check out our guide to see how to [get started](https://infisical.com/docs/contributing/getting-started).
Not sure where to get started? You can:

@ -60,7 +60,7 @@
"pino": "^8.16.1",
"pino-http": "^8.5.1",
"posthog-node": "^2.6.0",
"probot": "^12.3.1",
"probot": "^12.3.3",
"query-string": "^7.1.3",
"rate-limit-mongo": "^2.3.2",
"rimraf": "^3.0.2",
@ -5991,9 +5991,9 @@
}
},
"node_modules/@octokit/webhooks": {
"version": "9.26.0",
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz",
"integrity": "sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA==",
"version": "9.26.3",
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.3.tgz",
"integrity": "sha512-DLGk+gzeVq5oK89Bo601txYmyrelMQ7Fi5EnjHE0Xs8CWicy2xkmnJMKptKJrBJpstqbd/9oeDFi/Zj2pudBDQ==",
"dependencies": {
"@octokit/request-error": "^2.0.2",
"@octokit/webhooks-methods": "^2.0.0",
@ -16306,9 +16306,9 @@
}
},
"node_modules/probot": {
"version": "12.3.1",
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.1.tgz",
"integrity": "sha512-ECSgycmAC0ILEK6cOa+x3QPufP5JybsuohOFCYr3glQU5SkbmypZJE/Sfio9mxAFHK5LCXveIDsfZCxf6ck4JA==",
"version": "12.3.3",
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.3.tgz",
"integrity": "sha512-cdtKd+xISzi8sw6++BYBXleRknCA6hqUMoHj/sJqQBrjbNxQLhfeFCq9O2d0Z4eShsy5YFRR3MWwDKJ9uAE0CA==",
"dependencies": {
"@octokit/core": "^3.2.4",
"@octokit/plugin-enterprise-compatibility": "^1.2.8",
@ -16317,7 +16317,7 @@
"@octokit/plugin-retry": "^3.0.6",
"@octokit/plugin-throttling": "^3.3.4",
"@octokit/types": "^8.0.0",
"@octokit/webhooks": "^9.8.4",
"@octokit/webhooks": "^9.26.3",
"@probot/get-private-key": "^1.1.0",
"@probot/octokit-plugin-config": "^1.0.0",
"@probot/pino": "^2.2.0",
@ -23392,9 +23392,9 @@
}
},
"@octokit/webhooks": {
"version": "9.26.0",
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz",
"integrity": "sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA==",
"version": "9.26.3",
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.3.tgz",
"integrity": "sha512-DLGk+gzeVq5oK89Bo601txYmyrelMQ7Fi5EnjHE0Xs8CWicy2xkmnJMKptKJrBJpstqbd/9oeDFi/Zj2pudBDQ==",
"requires": {
"@octokit/request-error": "^2.0.2",
"@octokit/webhooks-methods": "^2.0.0",
@ -31039,9 +31039,9 @@
}
},
"probot": {
"version": "12.3.1",
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.1.tgz",
"integrity": "sha512-ECSgycmAC0ILEK6cOa+x3QPufP5JybsuohOFCYr3glQU5SkbmypZJE/Sfio9mxAFHK5LCXveIDsfZCxf6ck4JA==",
"version": "12.3.3",
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.3.tgz",
"integrity": "sha512-cdtKd+xISzi8sw6++BYBXleRknCA6hqUMoHj/sJqQBrjbNxQLhfeFCq9O2d0Z4eShsy5YFRR3MWwDKJ9uAE0CA==",
"requires": {
"@octokit/core": "^3.2.4",
"@octokit/plugin-enterprise-compatibility": "^1.2.8",
@ -31050,7 +31050,7 @@
"@octokit/plugin-retry": "^3.0.6",
"@octokit/plugin-throttling": "^3.3.4",
"@octokit/types": "^8.0.0",
"@octokit/webhooks": "^9.8.4",
"@octokit/webhooks": "^9.26.3",
"@probot/get-private-key": "^1.1.0",
"@probot/octokit-plugin-config": "^1.0.0",
"@probot/pino": "^2.2.0",

@ -51,7 +51,7 @@
"pino": "^8.16.1",
"pino-http": "^8.5.1",
"posthog-node": "^2.6.0",
"probot": "^12.3.1",
"probot": "^12.3.3",
"query-string": "^7.1.3",
"rate-limit-mongo": "^2.3.2",
"rimraf": "^3.0.2",

@ -4962,7 +4962,8 @@
},
"security": [
{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}
]
}

@ -2,7 +2,7 @@ import { Request, Response } from "express";
import { Types } from "mongoose";
import { standardRequest } from "../../config/request";
import { getApps, getTeams, revokeAccess } from "../../integrations";
import { Bot, IntegrationAuth, Workspace } from "../../models";
import { Bot, IIntegrationAuth, Integration, IntegrationAuth, Workspace } from "../../models";
import { EventType } from "../../ee/models";
import { IntegrationService } from "../../services";
import { EEAuditLogService } from "../../ee/services";
@ -130,7 +130,6 @@ export const oAuthExchange = async (req: Request, res: Response) => {
export const saveIntegrationToken = async (req: Request, res: Response) => {
// TODO: refactor
// TODO: check if access token is valid for each integration
let integrationAuth;
const {
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
@ -152,31 +151,21 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
if (!bot) throw new Error("Bot must be enabled to save integration access token");
integrationAuth = await IntegrationAuth.findOneAndUpdate(
{
workspace: new Types.ObjectId(workspaceId),
integration
},
{
workspace: new Types.ObjectId(workspaceId),
integration,
url,
namespace,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8,
...(integration === INTEGRATION_GCP_SECRET_MANAGER
? {
metadata: {
authMethod: "serviceAccount"
}
let integrationAuth = await new IntegrationAuth({
workspace: new Types.ObjectId(workspaceId),
integration,
url,
namespace,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8,
...(integration === INTEGRATION_GCP_SECRET_MANAGER
? {
metadata: {
authMethod: "serviceAccount"
}
: {})
},
{
new: true,
upsert: true
}
);
}
: {})
}).save();
// encrypt and save integration access details
if (refreshToken) {
@ -188,12 +177,12 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
// encrypt and save integration access details
if (accessId || accessToken) {
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
integrationAuth = (await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessId,
accessToken,
accessExpiresAt: undefined
});
})) as IIntegrationAuth;
}
if (!integrationAuth) throw new Error("Failed to save integration access token");
@ -1208,13 +1197,64 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
});
};
/**
* Delete all integration authorizations and integrations for workspace with id [workspaceId]
* with integration name [integration]
* @param req
* @param res
* @returns
*/
export const deleteIntegrationAuths = async (req: Request, res: Response) => {
const {
query: { integration, workspaceId }
} = await validateRequest(reqValidator.DeleteIntegrationAuthsV1, req);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations
);
const integrationAuths = await IntegrationAuth.deleteMany({
integration,
workspace: new Types.ObjectId(workspaceId)
});
const integrations = await Integration.deleteMany({
integration,
workspace: new Types.ObjectId(workspaceId)
});
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.UNAUTHORIZE_INTEGRATION,
metadata: {
integration
}
},
{
workspaceId: new Types.ObjectId(workspaceId)
}
);
return res.status(200).send({
integrationAuths,
integrations
});
}
/**
* Delete integration authorization with id [integrationAuthId]
* @param req
* @param res
* @returns
*/
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
export const deleteIntegrationAuthById = async (req: Request, res: Response) => {
const {
params: { integrationAuthId }
} = await validateRequest(reqValidator.DeleteIntegrationAuthV1, req);

@ -251,6 +251,21 @@ export const deleteIntegration = async (req: Request, res: Response) => {
});
if (!deletedIntegration) throw new Error("Failed to find integration");
const numOtherIntegrationsUsingSameAuth = await Integration.countDocuments({
integrationAuth: deletedIntegration.integrationAuth,
_id: {
$nin: [deletedIntegration._id]
}
});
if (numOtherIntegrationsUsingSameAuth === 0) {
// no other integrations are using the same integration auth
// -> delete integration auth associated with the integration being deleted
await IntegrationAuth.deleteOne({
_id: deletedIntegration.integrationAuth
});
}
await EEAuditLogService.createAuditLog(
req.authData,

@ -111,11 +111,17 @@ export const createSecretImp = async (req: Request, res: Response) => {
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment: secretImport.environment, secretPath: secretImport.secretPath })
);
}
const folders = await Folder.findOne({
@ -323,7 +329,7 @@ export const updateSecretImport = async (req: Request, res: Response) => {
authData: req.authData,
workspaceId: importSecDoc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, {
@ -331,6 +337,13 @@ export const updateSecretImport = async (req: Request, res: Response) => {
secretPath
})
);
secretImports.forEach(({ environment, secretPath }) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
})
}
const orderBefore = importSecDoc.imports;
@ -453,7 +466,7 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
authData: req.authData,
workspaceId: importSecDoc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, {
@ -620,7 +633,7 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
@ -677,7 +690,7 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
authData: req.authData,
workspaceId: importSecDoc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {

@ -550,7 +550,7 @@ export const attachIdentityUniversalAuth = async (req: Request, res: Response) =
// validate trusted ips
const reformattedClientSecretTrustedIps = clientSecretTrustedIps.map((clientSecretTrustedIp) => {
if (!plan.ipAllowlisting && clientSecretTrustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({
if (!plan.ipAllowlisting && (clientSecretTrustedIp.ipAddress !== "0.0.0.0/0" && clientSecretTrustedIp.ipAddress !== "::/0")) return res.status(400).send({
message: "Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
@ -564,7 +564,7 @@ export const attachIdentityUniversalAuth = async (req: Request, res: Response) =
});
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
if (!plan.ipAllowlisting && accessTokenTrustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({
if (!plan.ipAllowlisting && (accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" && accessTokenTrustedIp.ipAddress !== "::/0")) return res.status(400).send({
message: "Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
@ -750,7 +750,7 @@ export const updateIdentityUniversalAuth = async (req: Request, res: Response) =
let reformattedClientSecretTrustedIps;
if (clientSecretTrustedIps) {
reformattedClientSecretTrustedIps = clientSecretTrustedIps.map((clientSecretTrustedIp) => {
if (!plan.ipAllowlisting && clientSecretTrustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({
if (!plan.ipAllowlisting && (clientSecretTrustedIp.ipAddress !== "0.0.0.0/0" && clientSecretTrustedIp.ipAddress !== "::/0")) return res.status(400).send({
message: "Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
@ -767,7 +767,7 @@ export const updateIdentityUniversalAuth = async (req: Request, res: Response) =
let reformattedAccessTokenTrustedIps;
if (accessTokenTrustedIps) {
reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
if (!plan.ipAllowlisting && accessTokenTrustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({
if (!plan.ipAllowlisting && (accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" && accessTokenTrustedIp.ipAddress !== "::/0")) return res.status(400).send({
message: "Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});

@ -1,9 +1,13 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import {
IdentityMembershipOrg,
Membership,
IWorkspace,
Identity,
IdentityMembership,
IdentityMembershipOrg,
Membership,
MembershipOrg,
User,
Workspace
} from "../../models";
import { Role } from "../../ee/models";
@ -298,7 +302,8 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
#swagger.description = 'Return projects in organization that user is part of'
#swagger.security = [{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}]
#swagger.parameters['organizationId'] = {
@ -326,6 +331,7 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
}
}
*/
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgWorkspacesv2, req);
@ -351,13 +357,27 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
).map((w) => w._id.toString())
);
const workspaces = (
await Membership.find({
user: req.user._id
}).populate("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
let workspaces: IWorkspace[] = [];
if (req.authData.authPayload instanceof Identity) {
workspaces = (
await IdentityMembership.find({
identity: req.authData.authPayload._id
}).populate<{ workspace: IWorkspace }>("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
}
if (req.authData.authPayload instanceof User) {
workspaces = (
await Membership.find({
user: req.authData.authPayload._id
}).populate<{ workspace: IWorkspace }>("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
}
return res.status(200).send({
workspaces

@ -13,7 +13,7 @@ import {
ProjectPermissionSub,
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { ForbiddenError, subject } from "@casl/ability";
import { Types } from "mongoose";
/**
@ -86,6 +86,14 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
ProjectPermissionSub.ServiceTokens
);
scopes.forEach(({ environment, secretPath }) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: secretPath })
);
})
const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, await getSaltRounds());

@ -348,7 +348,7 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
}
*/
const {
query: { secretPath, environment, workspaceId, type, include_imports },
query: { secretPath, environment, workspaceId, type, include_imports, version },
params: { secretName }
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
@ -371,7 +371,8 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
type,
secretPath,
authData: req.authData,
include_imports
include_imports,
version
});
const key = await BotService.getWorkspaceKeyWithBot({
@ -865,7 +866,7 @@ export const getSecrets = async (req: Request, res: Response) => {
*/
export const getSecretByName = async (req: Request, res: Response) => {
const {
query: { secretPath, environment, workspaceId, type, include_imports },
query: { secretPath, environment, workspaceId, type, include_imports, version },
params: { secretName }
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
@ -888,7 +889,8 @@ export const getSecretByName = async (req: Request, res: Response) => {
type,
secretPath,
authData: req.authData,
include_imports
include_imports,
version
});
return res.status(200).send({

@ -579,7 +579,9 @@ export const getSecretsHelper = async ({
event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({ authData }),
properties: {
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
numberOfSecrets: shouldRecordK8Event
? approximateForNoneCapturedEvents
: secrets.length,
environment,
workspaceId,
folderId,
@ -611,42 +613,86 @@ export const getSecretHelper = async ({
type,
authData,
secretPath = "/",
include_imports = true
include_imports = true,
version
}: GetSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
workspaceId: new Types.ObjectId(workspaceId)
});
let secret: ISecret | null | undefined = null;
// if using service token filter towards the folderId by secretpath
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
// try getting personal secret first (if exists)
secret = await Secret.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: type ?? SECRET_PERSONAL,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
}).lean();
if (!secret) {
// case: failed to find personal secret matching criteria
// -> find shared secret matching criteria
if (version === undefined) {
secret = await Secret.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: SECRET_SHARED
type: type ?? SECRET_PERSONAL,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
}).lean();
} else {
const secretVersion = await SecretVersion.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: type ?? SECRET_PERSONAL,
version
}).lean();
if (secretVersion) {
secret = await new Secret({
...secretVersion,
_id: secretVersion?.secret
});
}
}
if (!secret) {
// case: failed to find personal secret matching criteria
// -> find shared secret matching criteria
if (version === undefined) {
secret = await Secret.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: SECRET_SHARED
}).lean();
} else {
const secretVersion = await SecretVersion.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: SECRET_SHARED,
version
}).lean();
if (secretVersion) {
secret = await new Secret({
...secretVersion,
_id: secretVersion?.secret
});
}
}
}
if (!secret && include_imports) {
// if still no secret found search in imported secret and retreive
secret = await getAnImportedSecret(secretName, workspaceId.toString(), environment, folderId);
secret = await getAnImportedSecret(
secretName,
workspaceId.toString(),
environment,
folderId,
version
);
}
if (!secret) throw SecretNotFoundError();
@ -1141,11 +1187,12 @@ const recursivelyExpandSecret = async (
const secRefKey = entities[entities.length - 1];
const val = await fetchCrossEnv(secRefEnv, secRefPath, secRefKey);
interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val);
if (val !== undefined) {
interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val);
}
}
}
}
expandedSec[key] = interpolatedValue;
return interpolatedValue;
};

@ -38,6 +38,7 @@ export interface GetSecretParams {
type?: "shared" | "personal";
authData: AuthData;
include_imports?: boolean;
version?: number;
}
export interface UpdateSecretParams {

@ -156,12 +156,20 @@ router.get(
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
);
router.delete(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.deleteIntegrationAuths
);
router.delete(
"/:integrationAuthId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.deleteIntegrationAuth
integrationAuthController.deleteIntegrationAuthById
);
export default router;

@ -1,5 +1,6 @@
import { Types } from "mongoose";
import { generateSecretBlindIndexHelper } from "../helpers";
import { SecretVersion } from "../ee/models";
import { Folder, ISecret, Secret, SecretImport } from "../models";
import { getFolderByPath } from "./FolderService";
@ -9,7 +10,8 @@ export const getAnImportedSecret = async (
secretName: string,
workspaceId: string,
environment: string,
folderId = "root"
folderId = "root",
version?: number
) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
@ -48,10 +50,26 @@ export const getAnImportedSecret = async (
});
if (importedSecByFid.length === 0) return;
const secret = await Secret.findOne({
workspace: workspaceId,
secretBlindIndex
}).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean()
let secret;
if (version === undefined) {
secret = await Secret.findOne({
workspace: workspaceId,
secretBlindIndex
}).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean()
} else {
const secretVersion = await SecretVersion.findOne({
workspace: workspaceId,
secretBlindIndex,
version
}).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean();
if (secretVersion) {
secret = await new Secret({
...secretVersion,
_id: secretVersion.secret,
});
}
}
return secret;
};

@ -76,10 +76,10 @@ export const initializeSamlStrategy = async () => {
if (!organization) return done(OrganizationNotFoundError());
const email = profile.email;
const email = profile?.email ?? profile?.emailAddress // emailRippling is added because in Rippling the field `email` reserved
const firstName = profile.firstName;
const lastName = profile.lastName;
let user = await User.findOne({
email
}).select("+publicKey");

@ -108,14 +108,14 @@ export const AddUniversalAuthToIdentityV1 = z.object({
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }]),
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim(),
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }]),
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTTL: z.number().int().min(1).refine(value => value !== 0, {
message: "accessTokenTTL must have a non zero number",
}).default(2592000),

@ -192,6 +192,13 @@ export const GetIntegrationAuthNorthflankSecretGroupsV1 = z.object({
})
});
export const DeleteIntegrationAuthsV1 = z.object({
query: z.object({
integration: z.string().trim(),
workspaceId: z.string().trim()
})
});
export const DeleteIntegrationAuthV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()

@ -246,7 +246,15 @@ export const GetSecretByNameRawV3 = z.object({
include_imports: z
.enum(["true", "false"])
.default("true")
.transform((value) => value === "true")
.transform((value) => value === "true"),
version: z
.string()
.trim()
.optional()
.transform((value) => value === undefined ? undefined : parseInt(value, 10))
.refine((value) => value === undefined || !isNaN(value), {
message: "Version must be a number",
})
})
});
@ -318,7 +326,15 @@ export const GetSecretByNameV3 = z.object({
include_imports: z
.enum(["true", "false"])
.default("true")
.transform((value) => value === "true")
.transform((value) => value === "true"),
version: z
.string()
.trim()
.optional()
.transform((value) => value === undefined ? undefined : parseInt(value, 10))
.refine((value) => value === undefined || !isNaN(value), {
message: "Version must be a number",
})
}),
params: z.object({
secretName: z.string().trim()

@ -1,4 +0,0 @@
FROM alpine
RUN apk add --no-cache tini
COPY infisical /bin/infisical
ENTRYPOINT ["/sbin/tini", "--", "/bin/infisical"]

9
cli/docker/alpine Normal file

@ -0,0 +1,9 @@
FROM alpine
RUN apk add --no-cache tini
## Upgrade OpenSSL libraries to mitigate known vulnerabilities as the current Alpine image has not been patched yet.
RUN apk update && apk upgrade --no-cache libcrypto3 libssl3
COPY infisical /bin/infisical
ENTRYPOINT ["/sbin/tini", "--", "/bin/infisical"]

@ -474,6 +474,7 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
SetBody(request).
SetQueryParam("workspaceId", request.WorkspaceId).
SetQueryParam("environment", request.Environment).
SetQueryParam("secretPath", request.SecretPath).
SetQueryParam("include_imports", "false").
Get(fmt.Sprintf("%v/v3/secrets/raw", config.INFISICAL_URL))

@ -5,6 +5,7 @@ package cmd
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
@ -37,7 +38,8 @@ type Config struct {
}
type InfisicalConfig struct {
Address string `yaml:"address"`
Address string `yaml:"address"`
ExitAfterAuth bool `yaml:"exit-after-auth"`
}
type AuthConfig struct {
@ -66,8 +68,9 @@ type SinkDetails struct {
}
type Template struct {
SourcePath string `yaml:"source-path"`
DestinationPath string `yaml:"destination-path"`
SourcePath string `yaml:"source-path"`
Base64TemplateContent string `yaml:"base64-template-content"`
DestinationPath string `yaml:"destination-path"`
}
func ReadFile(filePath string) ([]byte, error) {
@ -107,12 +110,7 @@ func appendAPIEndpoint(address string) string {
return address + "/api"
}
func ParseAgentConfig(filePath string) (*Config, error) {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
func ParseAgentConfig(configFile []byte) (*Config, error) {
var rawConfig struct {
Infisical InfisicalConfig `yaml:"infisical"`
Auth struct {
@ -123,7 +121,7 @@ func ParseAgentConfig(filePath string) (*Config, error) {
Templates []Template `yaml:"templates"`
}
if err := yaml.Unmarshal(data, &rawConfig); err != nil {
if err := yaml.Unmarshal(configFile, &rawConfig); err != nil {
return nil, err
}
@ -205,6 +203,35 @@ func ProcessTemplate(templatePath string, data interface{}, accessToken string)
return &buf, nil
}
func ProcessBase64Template(encodedTemplate string, data interface{}, accessToken string) (*bytes.Buffer, error) {
// custom template function to fetch secrets from Infisical
decoded, err := base64.StdEncoding.DecodeString(encodedTemplate)
if err != nil {
return nil, err
}
templateString := string(decoded)
secretFunction := secretTemplateFunction(accessToken)
funcs := template.FuncMap{
"secret": secretFunction,
}
templateName := "base64Template"
tmpl, err := template.New(templateName).Funcs(funcs).Parse(templateString)
if err != nil {
return nil, err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return nil, err
}
return &buf, nil
}
type TokenManager struct {
accessToken string
accessTokenTTL time.Duration
@ -219,10 +246,11 @@ type TokenManager struct {
newAccessTokenNotificationChan chan bool
removeClientSecretOnRead bool
cachedClientSecret string
exitAfterAuth bool
}
func NewTokenManager(fileDeposits []Sink, templates []Template, clientIdPath string, clientSecretPath string, newAccessTokenNotificationChan chan bool, removeClientSecretOnRead bool) *TokenManager {
return &TokenManager{filePaths: fileDeposits, templates: templates, clientIdPath: clientIdPath, clientSecretPath: clientSecretPath, newAccessTokenNotificationChan: newAccessTokenNotificationChan, removeClientSecretOnRead: removeClientSecretOnRead}
func NewTokenManager(fileDeposits []Sink, templates []Template, clientIdPath string, clientSecretPath string, newAccessTokenNotificationChan chan bool, removeClientSecretOnRead bool, exitAfterAuth bool) *TokenManager {
return &TokenManager{filePaths: fileDeposits, templates: templates, clientIdPath: clientIdPath, clientSecretPath: clientSecretPath, newAccessTokenNotificationChan: newAccessTokenNotificationChan, removeClientSecretOnRead: removeClientSecretOnRead, exitAfterAuth: exitAfterAuth}
}
func (tm *TokenManager) SetToken(token string, accessTokenTTL time.Duration, accessTokenMaxTTL time.Duration) {
@ -245,18 +273,26 @@ func (tm *TokenManager) GetToken() string {
// Fetches a new access token using client credentials
func (tm *TokenManager) FetchNewAccessToken() error {
clientIDAsByte, err := ReadFile(tm.clientIdPath)
if err != nil {
return fmt.Errorf("unable to read client id from file path '%s' due to error: %v", tm.clientIdPath, err)
clientID := os.Getenv("INFISICAL_UNIVERSAL_AUTH_CLIENT_ID")
if clientID == "" {
clientIDAsByte, err := ReadFile(tm.clientIdPath)
if err != nil {
return fmt.Errorf("unable to read client id from file path '%s' due to error: %v", tm.clientIdPath, err)
}
clientID = string(clientIDAsByte)
}
clientSecretAsByte, err := ReadFile(tm.clientSecretPath)
if err != nil {
if len(tm.cachedClientSecret) == 0 {
return fmt.Errorf("unable to read client secret from file and no cached client secret found: %v", err)
} else {
clientSecretAsByte = []byte(tm.cachedClientSecret)
clientSecret := os.Getenv("INFISICAL_UNIVERSAL_CLIENT_SECRET")
if clientSecret == "" {
clientSecretAsByte, err := ReadFile(tm.clientSecretPath)
if err != nil {
if len(tm.cachedClientSecret) == 0 {
return fmt.Errorf("unable to read client secret from file and no cached client secret found: %v", err)
} else {
clientSecretAsByte = []byte(tm.cachedClientSecret)
}
}
clientSecret = string(clientSecretAsByte)
}
// remove client secret after first read
@ -264,13 +300,10 @@ func (tm *TokenManager) FetchNewAccessToken() error {
os.Remove(tm.clientSecretPath)
}
clientId := string(clientIDAsByte)
clientSecret := string(clientSecretAsByte)
// save as cache in memory
tm.cachedClientSecret = clientSecret
err, loginResponse := universalAuthLogin(clientId, clientSecret)
err, loginResponse := universalAuthLogin(clientID, clientSecret)
if err != nil {
return err
}
@ -354,6 +387,11 @@ func (tm *TokenManager) ManageTokenLifecycle() {
}
}
if tm.exitAfterAuth {
time.Sleep(25 * time.Second)
os.Exit(0)
}
if accessTokenRefreshedTime.IsZero() {
accessTokenRefreshedTime = tm.accessTokenFetchedTime
} else {
@ -396,7 +434,14 @@ func (tm *TokenManager) FetchSecrets() {
token := tm.GetToken()
if token != "" {
for _, secretTemplate := range tm.templates {
processedTemplate, err := ProcessTemplate(secretTemplate.SourcePath, nil, token)
var processedTemplate *bytes.Buffer
var err error
if secretTemplate.SourcePath != "" {
processedTemplate, err = ProcessTemplate(secretTemplate.SourcePath, nil, token)
} else {
processedTemplate, err = ProcessBase64Template(secretTemplate.Base64TemplateContent, nil, token)
}
if err != nil {
log.Error().Msgf("template engine: unable to render secrets because %s. Will try again on next cycle", err)
@ -449,12 +494,37 @@ var agentCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag config")
}
if !FileExists(configPath) {
log.Error().Msgf("Unable to locate %s. The provided agent config file path is either missing or incorrect", configPath)
var agentConfigInBytes []byte
agentConfigInBase64 := os.Getenv("INFISICAL_AGENT_CONFIG_BASE64")
if agentConfigInBase64 == "" {
data, err := ioutil.ReadFile(configPath)
if err != nil {
if !FileExists(configPath) {
log.Error().Msgf("Unable to locate %s. The provided agent config file path is either missing or incorrect", configPath)
return
}
}
agentConfigInBytes = data
}
if agentConfigInBase64 != "" {
decodedAgentConfig, err := base64.StdEncoding.DecodeString(agentConfigInBase64)
if err != nil {
log.Error().Msgf("Unable to decode base64 config file because %v", err)
return
}
agentConfigInBytes = decodedAgentConfig
}
if !FileExists(configPath) && agentConfigInBase64 == "" {
log.Error().Msgf("No agent config file provided. Please provide a agent config file", configPath)
return
}
agentConfig, err := ParseAgentConfig(configPath)
agentConfig, err := ParseAgentConfig(agentConfigInBytes)
if err != nil {
log.Error().Msgf("Unable to prase %s because %v. Please ensure that is follows the Infisical Agent config structure", configPath, err)
return
@ -471,7 +541,7 @@ var agentCmd = &cobra.Command{
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
filePaths := agentConfig.Sinks
tm := NewTokenManager(filePaths, agentConfig.Templates, configUniversalAuthType.ClientIDPath, configUniversalAuthType.ClientSecretPath, tokenRefreshNotifier, configUniversalAuthType.RemoveClientSecretOnRead)
tm := NewTokenManager(filePaths, agentConfig.Templates, configUniversalAuthType.ClientIDPath, configUniversalAuthType.ClientSecretPath, tokenRefreshNotifier, configUniversalAuthType.RemoveClientSecretOnRead, agentConfig.Infisical.ExitAfterAuth)
go tm.ManageTokenLifecycle()
go tm.FetchSecrets()

@ -11,7 +11,6 @@ import (
"github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/posthog/posthog-go"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
@ -75,7 +74,7 @@ var exportCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, WorkspaceId: projectId, SecretsPath: secretsPath})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, WorkspaceId: projectId, SecretsPath: secretsPath}, "")
if err != nil {
util.HandleError(err, "Unable to fetch secrets")
}
@ -88,7 +87,7 @@ var exportCmd = &cobra.Command{
var output string
if shouldExpandSecrets {
substitutions := util.ExpandSecrets(secrets, infisicalToken)
substitutions := util.ExpandSecrets(secrets, infisicalToken, "")
output, err = formatEnvs(substitutions, format)
if err != nil {
util.HandleError(err)

@ -67,6 +67,11 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
projectConfigDir, err := cmd.Flags().GetString("project-config-dir")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
secretOverriding, err := cmd.Flags().GetBool("secret-overriding")
if err != nil {
util.HandleError(err, "Unable to parse flag")
@ -92,7 +97,7 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports}, projectConfigDir)
if err != nil {
util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid")
@ -105,7 +110,7 @@ var runCmd = &cobra.Command{
}
if shouldExpandSecrets {
secrets = util.ExpandSecrets(secrets, infisicalToken)
secrets = util.ExpandSecrets(secrets, infisicalToken, projectConfigDir)
}
secretsByKey := getSecretsByKeys(secrets)
@ -198,6 +203,7 @@ func init() {
runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")")
runCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs ")
runCmd.Flags().String("path", "/", "get secrets within a folder path")
runCmd.Flags().String("project-config-dir", "", "explicitly set the directory where the .infisical.json resides")
}
// Will execute a single command and pass in the given secrets into the process

@ -68,7 +68,7 @@ var secretsCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports}, "")
if err != nil {
util.HandleError(err)
}
@ -80,7 +80,7 @@ var secretsCmd = &cobra.Command{
}
if shouldExpandSecrets {
secrets = util.ExpandSecrets(secrets, infisicalToken)
secrets = util.ExpandSecrets(secrets, infisicalToken, "")
}
visualize.PrintAllSecretDetails(secrets)
@ -169,7 +169,7 @@ var secretsSetCmd = &cobra.Command{
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
// pull current secrets
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath}, "")
if err != nil {
util.HandleError(err, "unable to retrieve secrets")
}
@ -406,7 +406,7 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
util.HandleError(err, "Unable to parse path flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}, "")
if err != nil {
util.HandleError(err, "To fetch all secrets")
}
@ -455,7 +455,7 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}, "")
if err != nil {
util.HandleError(err, "To fetch all secrets")
}

@ -113,6 +113,28 @@ func GetWorkSpaceFromFile() (models.WorkspaceConfigFile, error) {
return workspaceConfigFile, nil
}
func GetWorkSpaceFromFilePath(configFileDir string) (models.WorkspaceConfigFile, error) {
configFilePath := filepath.Join(configFileDir, ".infisical.json")
_, configFileStatusError := os.Stat(configFilePath)
if os.IsNotExist(configFileStatusError) {
return models.WorkspaceConfigFile{}, fmt.Errorf("file %s does not exist", configFilePath)
}
configFileAsBytes, err := os.ReadFile(configFilePath)
if err != nil {
return models.WorkspaceConfigFile{}, err
}
var workspaceConfigFile models.WorkspaceConfigFile
err = json.Unmarshal(configFileAsBytes, &workspaceConfigFile)
if err != nil {
return models.WorkspaceConfigFile{}, err
}
return workspaceConfigFile, nil
}
// FindWorkspaceConfigFile searches for a .infisical.json file in the current directory and all parent directories.
func FindWorkspaceConfigFile() (string, error) {
dir, err := os.Getwd()

@ -105,6 +105,17 @@ func RequireLocalWorkspaceFile() {
}
}
func ValidateWorkspaceFile(projectConfigFilePath string) {
workspaceFilePath, err := GetWorkSpaceFromFilePath(projectConfigFilePath)
if err != nil {
PrintErrorMessageAndExit(fmt.Sprintf("error reading your project config %v", err))
}
if workspaceFilePath.WorkspaceId == "" {
PrintErrorMessageAndExit("Your project id is missing in your local config file. Please add it or run again [infisical init]")
}
}
func GetHashFromStringList(list []string) string {
hash := sha256.New()

@ -168,7 +168,7 @@ func GetPlainTextSecretsViaMachineIdentity(accessToken string, workspaceId strin
getSecretsRequest.SecretPath = secretsPath
}
rawSecrets, err := api.CallGetRawSecretsV3(httpClient, api.GetRawSecretsV3Request{WorkspaceId: workspaceId, SecretPath: environmentName, Environment: environmentName})
rawSecrets, err := api.CallGetRawSecretsV3(httpClient, api.GetRawSecretsV3Request{WorkspaceId: workspaceId, SecretPath: secretsPath, Environment: environmentName})
if err != nil {
return nil, err
}
@ -220,7 +220,7 @@ func InjectImportedSecret(plainTextWorkspaceKey []byte, secrets []models.SingleE
return secrets, nil
}
func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models.SingleEnvironmentVariable, error) {
func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectConfigFilePath string) ([]models.SingleEnvironmentVariable, error) {
var infisicalToken string
if params.InfisicalToken == "" {
infisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
@ -236,7 +236,13 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
if infisicalToken == "" {
if isConnected {
log.Debug().Msg("GetAllEnvironmentVariables: Connected to internet, checking logged in creds")
RequireLocalWorkspaceFile()
if projectConfigFilePath == "" {
RequireLocalWorkspaceFile()
} else {
ValidateWorkspaceFile(projectConfigFilePath)
}
RequireLogin()
}
@ -251,13 +257,26 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
}
workspaceFile, err := GetWorkSpaceFromFile()
if err != nil {
return nil, err
var infisicalDotJson models.WorkspaceConfigFile
if projectConfigFilePath == "" {
projectConfig, err := GetWorkSpaceFromFile()
if err != nil {
return nil, err
}
infisicalDotJson = projectConfig
} else {
projectConfig, err := GetWorkSpaceFromFilePath(projectConfigFilePath)
if err != nil {
return nil, err
}
infisicalDotJson = projectConfig
}
if params.WorkspaceId != "" {
workspaceFile.WorkspaceId = params.WorkspaceId
infisicalDotJson.WorkspaceId = params.WorkspaceId
}
// // Verify environment
@ -266,18 +285,18 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
// return nil, fmt.Errorf("unable to validate environment name because [err=%s]", err)
// }
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId,
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, infisicalDotJson.WorkspaceId,
params.Environment, params.TagSlugs, params.SecretsPath, params.IncludeImport)
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn)
backupSecretsEncryptionKey := []byte(loggedInUserDetails.UserCredentials.PrivateKey)[0:32]
if errorToReturn == nil {
WriteBackupSecrets(workspaceFile.WorkspaceId, params.Environment, backupSecretsEncryptionKey, secretsToReturn)
WriteBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, backupSecretsEncryptionKey, secretsToReturn)
}
// only attempt to serve cached secrets if no internet connection and if at least one secret cached
if !isConnected {
backedSecrets, err := ReadBackupSecrets(workspaceFile.WorkspaceId, params.Environment, backupSecretsEncryptionKey)
backedSecrets, err := ReadBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, backupSecretsEncryptionKey)
if len(backedSecrets) > 0 {
PrintWarning("Unable to fetch latest secret(s) due to connection error, serving secrets from last successful fetch. For more info, run with --debug")
secretsToReturn = backedSecrets
@ -421,7 +440,7 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod
return secretMapByName
}
func ExpandSecrets(secrets []models.SingleEnvironmentVariable, infisicalToken string) []models.SingleEnvironmentVariable {
func ExpandSecrets(secrets []models.SingleEnvironmentVariable, infisicalToken string, projectConfigPathDir string) []models.SingleEnvironmentVariable {
expandedSecs := make(map[string]string)
interpolatedSecs := make(map[string]string)
// map[env.secret-path][keyname]Secret
@ -454,7 +473,7 @@ func ExpandSecrets(secrets []models.SingleEnvironmentVariable, infisicalToken st
if crossRefSec, ok := crossEnvRefSecs[uniqKey]; !ok {
// if not in cross reference cache, fetch it from server
refSecs, err := GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: env, InfisicalToken: infisicalToken, SecretsPath: secPath})
refSecs, err := GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: env, InfisicalToken: infisicalToken, SecretsPath: secPath}, projectConfigPathDir)
if err != nil {
HandleError(err, fmt.Sprintf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath), "If you are using a service token to fetch secrets, please ensure it is valid")
}

@ -1,10 +1,4 @@
---
title: "Get Projects"
openapi: "GET /api/v2/organizations/{organizationId}/workspaces"
---
<Warning>
This endpoint will be deprecated in the near future in Q1/Q2 2024.
We recommend switching to using [identities](/documentation/platform/identities/overview).
</Warning>
---

@ -4,10 +4,20 @@ title: "Changelog"
The changelog below reflects new product developments and updates on a monthly basis.
## January 2024
- Reduced size of Infisical Node.js SDK by ≈90%.
- Added secret fallback support to all SDK's.
- Added Machine Identity support to [Terraform Provider](https://github.com/Infisical/terraform-provider-infisical).
- Released [.NET SDK](https://infisical.com/docs/sdks/languages/csharp).
- Added symmetric encryption support to all SDK's.
- Fixed secret reminders bug, where reminders were not being updated correctly.
## December 2023
- Added ability to [manage folders via CLI](https://infisical.com/docs/cli/commands/secrets).
- Released [(machine) identities](https://infisical.com/docs/documentation/platform/identities/overview) and [universal auth](https://infisical.com/docs/documentation/platform/identities/universal-auth) features.
- Created new cross-language SDKs for [Python](https://infisical.com/docs/sdks/languages/python), [Node](https://infisical.com/docs/sdks/languages/node), and [Java](https://infisical.com/docs/sdks/languages/java).
- Released first version of the [Infisical Agent](https://infisical.com/docs/infisical-agent/overview)
- Added ability to [manage folders via CLI](https://infisical.com/docs/cli/commands/secrets).
## November 2023

@ -62,6 +62,16 @@ Inject secrets from Infisical into your application process.
</Accordion>
### Flags
<Accordion title="--project-config-dir">
Explicitly set the directory where the .infisical.json resides. This is useful for some monorepo setups.
```bash
# Example
infisical run --project-config-dir=/some-dir -- printenv
```
</Accordion>
<Accordion title="--command">
Pass secrets into multiple commands at once

@ -6,7 +6,7 @@ description: "Frequently Asked Questions about contributing to Infisical"
Frequently asked questions about contributing to Infisical can be found on this page.
If you can't find the answer you are looking for, please create an issue on our GitHub repository or join our Slack channel for additional support.
<Accordion title="Error building backend (Alpine Linux CDN temporary error)">
<Accordion title="Error building Infisical platform backend (Alpine Linux CDN temporary error)">
The Alpine Linux CDN may be unavailable/down in your region infrequently (eg. there is an unplanned outage). One possible fix is to add a retry mechanism and a fallback mirrors array to the Dockerfile. You can also use this as an opportunity to pin the Alpine Linux version for Docker to use in case there are issues with the latest version. Ensure to use https for the mirrors.
#### Make the following changes to the backend Dockerfile

@ -1,14 +1,30 @@
---
title: "Overview"
description: "We welcome any contributions to Infisical, big or small."
description: "Contributing to the Infisical ecosystem."
---
To set a strong foundation, this section outlines how we, the community and members of Infisical,
should approach the development and contribution process.
## Code-bases
Infisical has two major code-bases. One for the platform code, and one for SDKs. The contribution process has some key differences between the two, so we've split the documentation into two sections:
- The [Infisical Platform](https://github.com/Infisical/infisical), the Infisical platform itself.
- The [Infisical SDK](https://github.com/Infisical/sdk), the official Infisical client SDKs.
<CardGroup cols={2}>
<Card title="Infisical Platform" href="/contributing/platform/developing" icon="layer-group" color="#A1B659">
The Infisical platform is the core of the Infisical ecosystem.
</Card>
<Card href="/contributing/sdk/developing" title="Infisical SDK" icon="code" color="#A1B659">
The SDKs are the official Infisical client libraries, used by developers to easily interact with the Infisical platform.
</Card>
</CardGroup>
## Community
We are building an inclusive community, and this means adhering to the [Code of Conduct](/contributing/code-of-conduct).
We are building an inclusive community, and this means adhering to the [Code of Conduct](/contributing/getting-started/code-of-conduct).
## Bugs and issues
@ -29,8 +45,10 @@ If you're ever in doubt about whether or not a proposed feature aligns with Infi
## Writing and submitting code
Anyone can contribute code to Infisical. To get started, check out the [local development guide](/contributing/developing), make your changes, and submit a pull request to the main repository
adhering to the [pull request guide](/contributing/pull-requests).
Anyone can contribute code to Infisical. To get started, check out the local development guides for each language.
- Local development guide for Platform is [here](/contributing/platform/developing).
- Local development guide for SDK is [here](/contributing/sdk/developing).
## Licensing
@ -38,3 +56,4 @@ adhering to the [pull request guide](/contributing/pull-requests).
Most of Infisical's code is under the MIT license, though some paid feature restrictions are covered by a proprietary license.
Any third party components incorporated into our code are licensed under the original license provided by the applicable component owner.

@ -19,7 +19,7 @@ You should follow the automatically-generated PR template to fill in the PR desc
Give a functional overview of how your feature works, including how the user can use the feature. Then share any technical details in an overview of how the PR works.
As of `06-01-2023`, all PRs created after this date are required to attach a video of you performing the described functionality.
As of `06-01-2023`, all PRs created after this date are required to attach a video of you performing the described functionality.
### Bug Fix PRs
@ -34,6 +34,8 @@ Once your PR is reviewed, one or two relevant members of the Infisical team shou
- Vlad: Frontend, Web UI
- Tony: Backend, SDKs, Security
- Maidul: Backend, CI/CD, CLI, Kubernetes Operator
- Daniel: Frontend, UI/UX, Backend, SDKs
The team member(s) will start by enabling baseline checks to ensure that there are no leaked secrets, new dependencies are clear, and the frontend/backend services start up. Afterward, they will review your PR thoroughly by testing the code and leave any feedback or work in with you to revise the PR up to standard.

@ -1,6 +1,6 @@
---
title: 'Local development'
description: 'This guide will help you set up and run Infisical in local development.'
description: 'This guide will help you set up and run the Infisical platform in local development.'
---
## Fork and clone the repo

@ -0,0 +1,408 @@
---
title: "Local development"
description: "This guide will help you contribute to the Infisical SDK."
---
## Fork and clone the repo
[Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the [repository](https://github.com/Infisical/sdk) to your own GitHub account and then [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) it to your local device.
Once, you've done that, create a new branch:
```console
git checkout -b MY_BRANCH_NAME
```
## Set up environment variables
Start by creating a .env file at the root of the Infisical directory then copy the contents of the file below into the .env file.
<Accordion title=".env file content">
```env
# This is required for running tests locally.
# Rename this file to ".env" and fill in the values below.
# Please make sure that the machine identity has access to the project you are testing in.
# https://infisical.com/docs/documentation/platform/identities/universal-auth
INFISICAL_UNIVERSAL_CLIENT_ID=MACHINE_IDENTITY_CLIENT_ID
INFISICAL_UNIVERSAL_CLIENT_SECRET=MACHINE_IDENTITY_CLIENT_SECRET
# The ID of the Infisical project where we will create the test secrets.
# NOTE: The project must have a dev environment. (This is created by default when you create a project.)
INFISICAL_PROJECT_ID=INFISICAL_TEST_PROJECT_ID
# The Infisical site URL. If you are testing with a local Infisical instance, then this should be set to "http://localhost:8080".
INFISICAL_SITE_URL=https://app.infisical.com
````
</Accordion>
<Warning>
The above values are required for running tests locally. Before opening a pull request, make sure to run `cargo test` to ensure that all tests pass.
</Warning>
## Guidelines
### Predictable and consistent
When adding new functionality (such as new functions), it's very important that the functionality is added to _all_ the SDK's. This is to ensure that the SDK's are predictable and consistent across all languages. If you are adding new functionality, please make sure to add it to all the SDK's.
### Handling errors
Error handling is very important when writing SDK's. We want to make sure that the SDK's are easy to use, and that the user gets a good understanding of what went wrong when something fails. When adding new functionality, please make sure to add proper error handling. [Read more about error handling here](#error-handling).
### Tests
If you add new functionality or modify existing functionality, please write tests thats properly cover the new functionality. You can run tests locally by running `cargo test` from the root directory. You must always run tests before opening a pull request.
### Code style
Please follow the default rust styling guide when writing code for the base SDK. [Read more about rust code style here](https://doc.rust-lang.org/nightly/style-guide/#the-default-rust-style).
## Prerequisites for contributing
### Understanding the terms
In the guide we use some terms that might be unfamiliar to you. Here's a quick explanation of the terms we use:
- **Base SDK**: The base SDK is the SDK that all other SDK's are built on top of. The base SDK is written in Rust, and is responsible for executing commands and parsing the input and output to and from JSON.
- **Commands**: Commands are what's being sent from the target language to the command handler. The command handler uses the command to execute the corresponding function in the base SDK. Commands are in reality just a JSON string that tells the command handler what function to execute, and what input to use.
- **Command handler**: The command handler is the part of the base SDK that takes care of executing commands. It also takes care of parsing the input and output to and from JSON.
- **Target language**: The target language refers to the actual SDK code. For example, the [Node.js SDK](https://www.npmjs.com/package/@infisical/sdk) is a "target language", and so is the [Python SDK](https://pypi.org/project/infisical-python/).
### Understanding the execution flow
After the target language SDK is initiated, it uses language-specific bindings to interact with the base SDK.
These bindings are instantiated, setting up the interface for command execution. A client within the command handler is created, which issues commands to the base SDK.
When a command is executed, it is first validated. If valid, the command handler locates the corresponding command to perform. If the command executes successfully, the command handler returns the output to the target language SDK, where it is parsed and returned to the user.
If the command handler fails to validate the input, an error will be returned to the target language SDK.
<Frame caption="Execution flow diagram for the SDK from the target language to the base SDK. The execution flow is the same for all target languages.">
<img height="640" width="520" src="/images/sdk-flow.png" />
</Frame>
### Rust knowledge
Contributing to the SDK requires intermediate to advanced knowledge of Rust concepts such as lifetimes, traits, generics, and async/await _(futures)_, and more.
### Rust setup
The base SDK is written in rust. Therefore you must have rustc and cargo installed. You can install rustc and cargo by following the instructions [here](https://www.rust-lang.org/tools/install).
You shouldn't have to use the rust cross compilation toolchain, as all compilation is done through a collection of Github Actions. However. If you need to test cross compilation, please do so with Github Actions.
### Tests
If you add new functionality or modify existing functionality, please write tests thats properly cover the new functionality. You can run tests locally by running `cargo test` from the root directory.
### Language-specific crates
The language-specific crates should ideally never have to be modified, as they are simply a wrapper for the `infisical-json` crate, which executes "commands" from the base SDK. If you need to create a new target-language specific crate, please try to create native bindings for the target language. Some languages don't have direct support for native bindings (Java as an example). In those cases we can use the C bindings (`crates/infisical-c`) in the target language.
## Generate types
Having almost seemless type safety from the base SDK to the target language is critical, as writing types for each language has a lot of drawbacks such as duplicated code, and lots of overhead trying to keep the types up-to-date and in sync across a large collection of languages. Therefore we decided to use [QuickType](https://quicktype.io/) and [Serde](https://serde.rs/) to help us generate types for each language. In our Rust base SDK (`crates/infisical`), we define all the inputs/outputs.
If you are interested in reading about QuickType works under the hood, you can [read more here](http://blog.quicktype.io/under-the-hood/).
This is an example of a type defined in Rust (both input and output). For this to become a generated type, you'll need to add it to our schema generator. More on that further down.
```rust
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase")]
// Input:
pub struct CreateSecretOptions {
pub environment: String, // environment
pub secret_comment: Option<String>, // secretComment
pub path: Option<String>, // secretPath
pub secret_value: String, // secretValue
pub skip_multiline_encoding: Option<bool>, // skipMultilineEncoding
pub r#type: Option<String>, // shared / personal
pub project_id: String, // workspaceId
pub secret_name: String, // secretName (PASSED AS PARAMETER IN REQUEST)
}
// Output:
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateSecretResponse {
pub secret: Secret, // "Secret" is defined elsewhere.
}
````
### Adding input types to the schema generator
You will _only_ have to define outputs in our schema generator, then QuickType will take care of the rest behind the scenes. You can find the Rust crate that takes care of type generation here: `crates/sdk-schemas/src/main.rs`.
Simply add the output _(also called response)_, to the `write_schema_for_response!` macro. This will let QuickType know that it should generate types for the given structs. The main function will look something like this:
```rust
fn main() -> Result<()> {
// Input types for new Client
write_schema_for!(infisical_json::client::ClientSettings);
// Input types for Client::run_command
write_schema_for!(infisical_json::command::Command);
// Output types for Client::run_command
// Only add structs which are direct results of SDK commands.
write_schema_for_response! {
infisical::manager::secrets::GetSecretResponse,
infisical::manager::secrets::ListSecretsResponse,
infisical::manager::secrets::UpdateSecretResponse,
infisical::manager::secrets::DeleteSecretResponse,
infisical::manager::secrets::CreateSecretResponse, // <-- This is the output from the above example!
infisical::auth::AccessTokenSuccessResponse
};
Ok(())
}
```
### Generating the types for the target language
Once you've added the output to the schema generator, you can generate the types for the target language by running the following command from the root directory:
```console
$ npm install
$ npm run schemas
```
<Warning>If you change any of the structs defined in the base SDK, you will need to run this script to re-generate the types.</Warning>
This command will run the `schemas.ts` file found in the `support/scripts` folder. If you are adding a new language, it's important that you add the language to the code.
This is an example of how how we generate types for Node.js:
```ts
const ts = await quicktype({
inputData,
lang: "typescript",
rendererOptions: {}
});
await ensureDir("./languages/node/src/infisical_client");
writeToFile("./languages/node/src/infisical_client/schemas.ts", ts.lines);
```
## Building bindings
We've tried to streamline the building process as much as possible. So you shouldn't have to worry much about building bindings, as it should just be a few commands.
### Node.js
Building bindings for Node.js is very straight foward. The command below will generate NAPI bindings for Node.js, and move the bindings to the correct folder. We use [NAPI-RS](https://napi.rs/) to generate the bindings.
```console
$ cd languages/node
$ npm run build
```
### Python
To generate and use python bindings you will need to run the following commands.
The Python SDK is located inside the crates folder. This is a limitation of the maturin tool, forcing us to structure the project in this way.
```console
$ pip install -U pip maturin
$ cd crates/infisical-py
$ python3 -m venv .venv
$ source .venv/bin/activate
$ maturin develop
```
<Warning>
After running the commands above, it's very important that you rename the generated .so file to `infisical_py.so`. After renaming it you also need to move it into the root of the `crates/infisical-py` folder.
</Warning>
### Java
Java uses the C bindings to interact with the base SDK. To build and use the C bindings in Java, please follow the instructions below.
```console
$ cd crates/infisical-c
$ cargo build --release
$ cd ../../languages/java
```
<Warning>
After generating the C bindings, the generated .so or .dll has been created in the `/target` directory at the root of the project.
You have to manually move the generated file into the `languages/java/src/main/resources` directory.
</Warning>
## Error handling
### Error handling in the base SDK
The base SDK should never panic. If an error occurs, we should return a `Result` with an error message. We have a custom Result type defined in the `error.rs` file in the base SDK.
All our errors are defined in an enum called `Error`. The `Error` enum is defined in the `error.rs` file in the base SDK. The `Error` enum is used in the `Result` type, which is used as the return type for all functions in the base SDK.
```rust
#[derive(Debug, Error)]
pub enum Error {
// Secret not found
#[error("Secret with name '{}' not found.", .secret_name)]
SecretNotFound { secret_name: String },
// .. other errors
// Errors that are not specific to the base SDK.
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
#[error(transparent)]
Serde(#[from] serde_json::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
}
```
### Returning an error
You can find many examples of how we return errors in the SDK code. A relevant example is for creating secrets, which can be found in `crates/infisical/src/api/secrets/create_secret.rs`. When the error happened due to a request error to our API, we have an API error handler. This prevents duplicate code and keeps error handling consistent across the SDK. You can find the api error handler in the `error.rs` file.
### Error handling in the target language SDK's.
All data sent to the target language SDK has the same format. The format is an object with 3 fields: `success (boolean)`, `data (could be anything or nothing)`, and `errorMessage (string or null)`.
The `success` field is used to determine if the request was successful or not. The `data` field is used to return data from the SDK. The `errorMessage` field is used to return an error message if the request was not successful.
This means that if the success if false or if the error message is not null, something went wrong and we should throw an error on the target-language level, with the error message.
## Command handler
### What is the command handler
The command handler (the `infisical-json` crate), takes care of executing commands sent from the target language. It also takes care of parsing the input and output to and from JSON. The command handler is the only part of the base SDK that should be aware of JSON. The rest of the base SDK should be completely unaware of JSON, and only work with the Rust structs defined in the base SDK.
The command handler exposes a function called `run_command`, which is what we use in the target language to execute commands. The function takes a json string as input, and returns a json string as output. We use helper functions generated by QuickType to convert the input and output to and from JSON.
### Creating new SDK methods
Creating new commands is necessary when adding new methods to the SDK's. Defining a new command is a 3-step process in most cases.
#### 1. Define the input and output structs
Earlier in this guide, we defined the input and output structs for the `CreateSecret` command. We will use that as an example here as well.
#### 2. Creating the method in the base SDK
The first step is to create the method in the base SDK. This step will be different depending on what method you are adding. In this example we're going to assume you're adding a function for creating a new secret.
After you created the function for creating the secret, you'll need need to add it to the ClientSecrets implementation. We do it this way to keep the code organized and easy to read. The ClientSecrets struct is located in the `crates/infisical/src/manager/secrets.rs` file.
```rust
pub struct ClientSecrets<'a> {
pub(crate) client: &'a mut crate::Client,
}
impl<'a> ClientSecrets<'a> {
pub async fn create(&mut self, input: &CreateSecretOptions) -> Result<CreateSecretResponse> {
create_secret(self.client, input).await // <-- This is the function you created!
}
}
impl<'a> Client {
pub fn secrets(&'a mut self) -> ClientSecrets<'a> {
ClientSecrets { client: self }
}
}
```
#### 3. Define a new command
We define new commands in the `crates/infisical-json/src/command.rs` file. The `Command` enum is what we use to define new commands.
In the codesnippet below we define a new command called `CreateSecret`. The `CreateSecret` command takes a `CreateSecretOptions` struct as input. We don't have to define the output, because QuickType's converter helps us with figuring out the return type for each command.
````rust
```rust
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub enum Command {
GetSecret(GetSecretOptions),
ListSecrets(ListSecretsOptions),
CreateSecret(CreateSecretOptions), // <-- The new command!
UpdateSecret(UpdateSecretOptions),
DeleteSecret(DeleteSecretOptions),
}
````
#### 4. Add the command to the command handler
After defining the command, we need to add it to the command handler itself. This takes place in the `crates/infisical-json/src/client.rs` file. The `run_command` function is what we use to execute commands.
In the Client implementation we try to parse the JSON string into a `Command` enum. If the parsing is successful, we match the command and execute the corresponding function.
```rust
match cmd {
Command::GetSecret(req) => self.0.secrets().get(&req).await.into_string(),
Command::ListSecrets(req) => self.0.secrets().list(&req).await.into_string(),
Command::UpdateSecret(req) => self.0.secrets().update(&req).await.into_string(),
Command::DeleteSecret(req) => self.0.secrets().delete(&req).await.into_string(),
// This is the new command:
Command::CreateSecret(req) => self.0.secrets().create(&req).await.into_string(),
}
```
#### 5. Implementing the new command in the target language SDK's
We did it! We've now added a new command to the base SDK. The last step is to implement the new command in the target language SDK's. The process is a little different from language to language, but in this example we're going to assume that we're adding a new command to the Node.js SDK.
First you'll need to generate the new type schemas, we added a new command, input struct, and output struct. [Read more about generating types here](#generating-the-types-for-the-target-language).
Secondly you need to build the new node bindings so we can use the new functionality in the Node.js SDK. You can do this by running the following command from the `languages/node` directory:
```console
$ npm install
$ npm run build
```
The build command will execute a build script in the `infisical-napi` crate, and move the generated bindings to the appropriate folder.
After building the new bindings, you can access the new functionality in the Node.js SDK source.
```ts
// 'binding' is a js file that makes it easier to access the methods in the bindings. (it's auto generated when running npm run build)
import * as rust from "../../binding";
// We can import the newly generated types from the schemas.ts file. (Generated with QuickType!)
import type { CreateSecretOptions, CreateSecretResponse } from "./schemas";
// This is the QuickType converter that we use to create commands with! It takes care of all JSON parsing and serialization.
import { Convert, ClientSettings } from "./schemas";
export class InfisicalClient {
#client: rust.Client;
constructor(settings: ClientSettings) {
const settingsJson = settings == null ? null : Convert.clientSettingsToJson(settings);
this.#client = new rust.InfisicalClient(settingsJson);
}
// ... getSecret
// ... listSecrets
// ... updateSecret
// ... deleteSecret
async createSecret(options: CreateSecretOptions): Promise<CreateSecretResponse["secret"]> {
// The runCommand will return a JSON string, which we can parse into a CreateSecretResponse.
const command = await this.#client.runCommand(
Convert.commandToJson({
createSecret: options
})
);
const response = Convert.toResponseForCreateSecretResponse(command); // <-- This is the QuickType converter in action!
// If the response is not successful or the data is null, we throw an error.
if (!response.success || response.data == null) {
throw new Error(response.errorMessage ?? "Something went wrong");
}
// To make it easier to work with the response, we return the secret directly.
return response.data.secret;
}
}
```
And that's it! We've now added a new command to the base SDK, and implemented it in the Node.js SDK. The process is very similar for all other languages, but the code will look a little different.
## Conclusion
The SDK has a lot of moving parts, and it can be a little overwhelming at first. But once you get the hang of it, it's actually quite simple. If you have any questions, feel free to reach out to us on [Slack](https://infisical.com/slack), or [open an issue](https://github.com/Infisical/sdk/issues) on GitHub.

@ -16,5 +16,6 @@ Follow the instructions for your language use the SDK for it:
- [Node SDK](https://infisical.com/docs/sdks/languages/node)
- [Python SDK](https://infisical.com/docs/sdks/languages/python)
- [Java SDK](https://infisical.com/docs/sdks/languages/java)
- [.NET SDK](https://infisical.com/docs/sdks/languages/csharp)
Missing a language? [Throw in a request](https://github.com/Infisical/infisical/issues).

@ -5,7 +5,7 @@ title: "Node"
This guide demonstrates how to use Infisical to manage secrets for your Node stack from local development to production. It uses:
- Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets.
- The [infisical-node](https://github.com/Infisical/infisical-node) client SDK to fetch secrets back to your Node application on demand.
- The [@infisical/sdk](https://github.com/Infisical/sdk/tree/main/languages/node) Node.js client SDK to fetch secrets back to your Node application on demand.
## Project Setup
@ -17,13 +17,11 @@ To begin, we need to set up a project in Infisical and add secrets to an environ
2. Add a secret to the development environment of this project so we can pull it back for local development. In the **Secrets Overview** page, press **Explore Development** and add a secret with the key `NAME` and value `YOUR_NAME`.
### Create an Infisical Token
### Create a Machine Identity
Now that we've created a project and added a secret to its development environment, we need to provision an Infisical Token that our Node application can use to access the secret.
Now that we've created a project and added a secret to its development environment, we need to configure an Infisical Machine Identity that our Node application can use to access the secret.
1. Head to the **Project Settings > Service Tokens** and press **Add New Token**.
2. Call the token anything like **My App Token** and select **Development** under **Environment**.
3. Copy the token and keep it handy.
- [How to setup machine identities](/documentation/platform/identities/overview)
## Create a Node app
@ -41,27 +39,43 @@ npm init -y
Install `express` and [infisical-node](https://github.com/Infisical/infisical-node), the client Node SDK for Infisical.
```console
npm install express infisical-node
npm install express @infisical/sdk
```
Finally, create an index.js file containing the application code.
```js
const express = require("express");
const express = require('express');
const { InfisicalClient, LogLevel } = require("@infisical/sdk");
const app = express();
const PORT = 3000;
const client = new InfisicalClient({
token: "YOUR_INFISICAL_TOKEN"
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
logLevel: LogLevel.Error
});
app.get("/", async (req, res) => {
const name = (await client.getSecret("NAME")).secretValue;
res.send(`Hello, ${name}!`);
// access value
const name = await client.getSecret({
environment: "dev",
projectId: "PROJECT_ID",
path: "/",
type: "shared",
secretName: "NAME"
});
res.send(`Hello! My name is: ${name.secretValue}`);
});
app.listen(PORT, () => {
console.log(`Example app listening on port ${PORT}`);
app.listen(PORT, async () => {
// initialize client
console.log(`App listening on port ${port}`);
});
```
@ -82,13 +96,6 @@ At this stage, you know how to fetch secrets from Infisical back to your Node ap
## FAQ
<AccordionGroup>
<Accordion title="Are my secrets exposed in transit every time the SDK fetches them?">
No. Infisical uses end-to-end encryption which ensures that secrets are always encrypted in transit
and decrypted on the client side. In fact, not even the server can decrypt your secrets (unless
that permission is explicitly granted from within the platform).
Check out the [security guide](/security/overview).
</Accordion>
<Accordion title="Isn't it inefficient if my app makes a request every time it needs a secret?">
The client SDK caches every secret and implements a 5-minute waiting period before
re-requesting it. The waiting period can be controlled by setting the `cacheTTL` parameter at
@ -98,10 +105,6 @@ At this stage, you know how to fetch secrets from Infisical back to your Node ap
The SDK caches every secret and falls back to the cached value if a request fails. If no cached
value ever-existed, the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="Can I still use process.env with the SDK?">
Yes. If no `token` parameter is passed in at the time of initializing the client or nothing is found when requesting for a secret,
then the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="What's the point if I still have to manage a token for the SDK?">
The token enables the SDK to authenticate with Infisical to fetch back your secrets.
Although the SDK requires you to pass in a token, it enables greater efficiency and security
@ -118,4 +121,4 @@ At this stage, you know how to fetch secrets from Infisical back to your Node ap
See also:
- Explore the [Node SDK](https://github.com/Infisical/infisical-node)
- Explore the [Node SDK](https://github.com/Infisical/sdk/tree/main/languages/node)

@ -5,7 +5,7 @@ title: "Python"
This guide demonstrates how to use Infisical to manage secrets for your Python stack from local development to production. It uses:
- Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets.
- The [infisical-python](https://github.com/Infisical/infisical-python) client SDK to fetch secrets back to your Python application on demand.
- The [infisical-python](https://github.com/Infisical/sdk/tree/main/crates/infisical-py) Python client SDK to fetch secrets back to your Python application on demand.
## Project Setup
@ -17,13 +17,11 @@ To begin, we need to set up a project in Infisical and add secrets to an environ
2. Add a secret to the development environment of this project so we can pull it back for local development. In the **Secrets Overview** page, press **Explore Development** and add a secret with the key `NAME` and value `YOUR_NAME`.
### Create an Infisical Token
### Create a Machine Identity
Now that we've created a project and added a secret to its development environment, we need to provision an Infisical Token that our Node application can use to access the secret.
Now that we've created a project and added a secret to its development environment, we need to configure an Infisical Machine Identity that our Python application can use to access the secret.
1. Head to the **Project Settings > Service Tokens** and press **Add New Token**.
2. Call the token anything like **My App Token** and select **Development** under **Environment**.
3. Copy the token and keep it handy.
- [How to setup machine identities](/documentation/platform/identities/overview)
## Create a Python app
@ -38,27 +36,36 @@ python3 -m venv env
source env/bin/activate
```
Install Flask and [infisical-python](https://github.com/Infisical/infisical-python), the client Python SDK for Infisical.
Install Flask and [infisical-python](https://github.com/Infisical/sdk/tree/main/crates/infisical-py), the client Python SDK for Infisical.
```console
pip install Flask infisical
pip install Flask infisical-python
```
Finally, create an `app.py` file containing the application code.
```python
```py
from flask import Flask
from infisical import InfisicalClient
from infisical_client import ClientSettings, InfisicalClient, GetSecretOptions
app = Flask(__name__)
client = InfisicalClient(token="your_infisical_token")
client = InfisicalClient(ClientSettings(
client_id="MACHINE_IDENTITY_CLIENT_ID",
client_secret="MACHINE_IDENTITY_CLIENT_SECRET",
))
@app.route("/")
def hello_world():
# access value
name = client.get_secret("NAME")
return f"Hello, {name.secret_value}!"
name = client.getSecret(options=GetSecretOptions(
environment="dev",
project_id="PROJECT_ID",
secret_name="NAME"
))
return f"Hello! My name is: {name.secret_value}"
```
Here, we initialized a `client` instance of the Infisical Python SDK with the Infisical Token
@ -78,13 +85,6 @@ At this stage, you know how to fetch secrets from Infisical back to your Python
## FAQ
<AccordionGroup>
<Accordion title="Are my secrets exposed in transit every time the SDK fetches them?">
No. Infisical uses end-to-end encryption which ensures that secrets are always encrypted in transit
and decrypted on the client side. In fact, not even the server can decrypt your secrets (unless
that permission is explicitly granted from within the platform).
Check out the [security guide](/security/overview).
</Accordion>
<Accordion title="Isn't it inefficient if my app makes a request every time it needs a secret?">
The client SDK caches every secret and implements a 5-minute waiting period before
re-requesting it. The waiting period can be controlled by setting the `cacheTTL` parameter at
@ -94,10 +94,6 @@ At this stage, you know how to fetch secrets from Infisical back to your Python
The SDK caches every secret and falls back to the cached value if a request fails. If no cached
value ever-existed, the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="Can I still use process.env with the SDK?">
Yes. If no `token` parameter is passed in at the time of initializing the client or nothing is found when requesting for a secret,
then the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="What's the point if I still have to manage a token for the SDK?">
The token enables the SDK to authenticate with Infisical to fetch back your secrets.
Although the SDK requires you to pass in a token, it enables greater efficiency and security
@ -114,6 +110,6 @@ At this stage, you know how to fetch secrets from Infisical back to your Python
See also:
- Explore the [Python SDK](https://github.com/Infisical/infisical-python)
- Explore the [Python SDK](https://github.com/Infisical/sdk/tree/main/crates/infisical-py)

@ -89,6 +89,10 @@ Then:
- If user A fetches the secret D back, they get the value F.
- If users B and C fetch the secret D back, they both get the value E.
<Info>
Please keep in mind that secret reminders won't work with personal overrides.
</Info>
![project override secret](../../images/platform/project/project-secrets-override.png)
### Drawer

@ -7,9 +7,8 @@ description: "Log in to Infisical via SSO protocols"
Infisical offers Google SSO and GitHub SSO for free across both Infisical Cloud and Infisical Self-hosted.
Infisical also offers SAML SSO authentication but as paid features that can be unlocked on Infisical Cloud's **Pro** tier
or via enterprise license on self-hosted instances of Infisical. On this front, we currently support Okta, Azure AD, and JumpCloud and
are expanding support for other IdPs in the coming months; stay tuned and feel free to request a IdP at this
[issue](https://github.com/Infisical/infisical/issues/442).
or via enterprise license on self-hosted instances of Infisical. On this front, we support industry-leading providers including
Okta, Azure AD, and JumpCloud; with any questions, please reach out to [sales@infisical.com](mailto:sales@infisical.com).
</Warning>
You can configure your organization in Infisical to have members authenticate with the platform via protocols like [SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0).
@ -22,4 +21,4 @@ your IdP cannot and will not have access to the decryption key needed to decrypt
- [GitLab SSO](/documentation/platform/sso/gitlab)
- [Okta SAML](/documentation/platform/sso/okta)
- [Azure SAML](/documentation/platform/sso/azure)
- [JumpCloud SAML](/documentation/platform/sso/jumpcloud)
- [JumpCloud SAML](/documentation/platform/sso/jumpcloud)

BIN
docs/images/sdk-flow.png Normal file

Binary file not shown.

After

(image error) Size: 881 KiB

@ -448,13 +448,30 @@
"pages": ["changelog/overview"]
},
{
"group": "Contributing",
"group": "",
"pages": [
"contributing/overview",
"contributing/code-of-conduct",
"contributing/developing",
"contributing/pull-requests",
"contributing/faq"
{
"group": "Getting Started",
"pages": [
"contributing/getting-started/overview",
"contributing/getting-started/code-of-conduct",
"contributing/getting-started/pull-requests",
"contributing/getting-started/faq"
]
},
{
"group": "Contributing to platform",
"pages": [
"contributing/platform/developing"
]
},
{
"group": "Contributing to SDK",
"pages": [
"contributing/sdk/developing"
]
}
]
}
],

@ -0,0 +1,394 @@
---
title: "Infisical .NET SDK"
icon: "C#"
---
If you're working with C#, the official [Infisical C# SDK](https://github.com/Infisical/sdk/tree/main/languages/csharp) package is the easiest way to fetch and work with secrets for your application.
- [Nuget Package](https://www.nuget.org/packages/Infisical.Sdk)
- [Github Repository](https://github.com/Infisical/sdk/tree/main/languages/csharp)
## Basic Usage
```cs
using Infisical.Sdk;
namespace Example
{
class Program
{
static void Main(string[] args)
{
var settings = new ClientSettings
{
ClientId = "CLIENT_ID",
ClientSecret = "CLIENT_SECRET",
// SiteUrl = "http://localhost:8080", <-- This line can be omitted if you're using Infisical Cloud.
};
var infisical = new InfisicalClient(settings);
var options = new GetSecretOptions
{
SecretName = "TEST",
ProjectId = "PROJECT_ID",
Environment = "dev",
};
var secret = infisical.GetSecret(options);
Console.WriteLine($"The value of secret '{secret.SecretKey}', is: {secret.SecretValue}");
}
}
}
```
This example demonstrates how to use the Infisical C# SDK in a C# application. The application retrieves a secret named `TEST` from the `dev` environment of the `PROJECT_ID` project.
<Warning>
We do not recommend hardcoding your [Machine Identity Tokens](/platform/identities/overview). Setting it as an environment variable would be best.
</Warning>
# Installation
Run `npm` to add `@infisical/sdk` to your project.
```console
$ dotnet add package Infisical.Sdk
```
# Configuration
Import the SDK and create a client instance with your [Machine Identity](/platform/identities/universal-auth).
```cs
using Infisical.Sdk;
namespace Example
{
class Program
{
static void Main(string[] args)
{
var settings = new ClientSettings
{
ClientId = "CLIENT_ID",
ClientSecret = "CLIENT_SECRET",
};
var infisical = new InfisicalClient(settings); // <-- Your SDK instance!
}
}
}
```
### ClientSettings methods
<ParamField query="options" type="object">
<Expandable title="properties">
<ParamField query="ClientId" type="string" optional>
Your machine identity client ID.
</ParamField>
<ParamField query="ClientSecret" type="string" optional>
Your machine identity client secret.
</ParamField>
<ParamField query="AccessToken" type="string" optional>
An access token obtained from the machine identity login endpoint.
</ParamField>
<ParamField query="CacheTtl" type="number" default="300" optional>
Time-to-live (in seconds) for refreshing cached secrets.
If manually set to 0, caching will be disabled, this is not recommended.
</ParamField>
<ParamField query="SiteUrl()" type="string" default="https://app.infisical.com" optional>
Your self-hosted absolute site URL including the protocol (e.g. `https://app.infisical.com`)
</ParamField>
</Expandable>
</ParamField>
### Caching
To reduce the number of API requests, the SDK temporarily stores secrets it retrieves. By default, a secret remains cached for 5 minutes after it's first fetched. Each time it's fetched again, this 5-minute timer resets. You can adjust this caching duration by setting the "cacheTTL" option when creating the client.
## Working with Secrets
### client.ListSecrets(options)
```cs
var options = new ListSecretsOptions
{
ProjectId = "PROJECT_ID",
Environment = "dev",
Path = "/foo/bar",
AttachToProcessEnv = false,
};
var secrets = infisical.ListSecrets(options);
```
Retrieve all secrets within the Infisical project and environment that client is connected to
#### Parameters
<ParamField query="Parameters" type="object">
<Expandable title="properties">
<ParamField query="Environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="ProjectId" type="string">
The project ID where the secret lives in.
</ParamField>
<ParamField query="Path" type="string" optional>
The path from where secrets should be fetched from.
</ParamField>
<ParamField query="AttachToProcessEnv" type="boolean" default="false" optional>
Whether or not to set the fetched secrets to the process environment. If true, you can access the secrets like so `System.getenv("SECRET_NAME")`.
</ParamField>
<ParamField query="IncludeImports" type="boolean" default="false" optional>
Whether or not to include imported secrets from the current path. Read about [secret import](/documentation/platform/secret-reference)
</ParamField>
</Expandable>
</ParamField>
### client.GetSecret(options)
```cs
var options = new GetSecretOptions
{
SecretName = "AAAA",
ProjectId = "659c781eb2d4fe3e307b77bd",
Environment = "dev",
};
var secret = infisical.GetSecret(options);
```
Retrieve a secret from Infisical.
By default, `GetSecret()` fetches and returns a shared secret.
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
<ParamField query="SecretName" type="string" required>
The key of the secret to retrieve.
</ParamField>
<ParamField query="ProjectId" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="Environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="Path" type="string" optional>
The path from where secret should be fetched from.
</ParamField>
<ParamField query="Type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
</ParamField>
</Expandable>
</ParamField>
### client.CreateSecret(options)
```cs
var options = new CreateSecretOptions {
Environment = "dev",
ProjectId = "PROJECT_ID",
SecretName = "NEW_SECRET",
SecretValue = "NEW_SECRET_VALUE",
SecretComment = "This is a new secret",
};
var newSecret = infisical.CreateSecret(options);
```
Create a new secret in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
<ParamField query="SecretName" type="string" required>
The key of the secret to create.
</ParamField>
<ParamField query="SecretValue" type="string" required>
The value of the secret.
</ParamField>
<ParamField query="ProjectId" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="Environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="Path" type="string" optional>
The path from where secret should be created.
</ParamField>
<ParamField query="Type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
</ParamField>
</Expandable>
</ParamField>
### client.UpdateSecret(options)
```cs
var options = new UpdateSecretOptions {
Environment = "dev",
ProjectId = "PROJECT_ID",
SecretName = "SECRET_TO_UPDATE",
SecretValue = "NEW VALUE"
};
var updatedSecret = infisical.UpdateSecret(options);
```
Update an existing secret in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
<ParamField query="SecretName" type="string" required>
The key of the secret to update.
</ParamField>
<ParamField query="SecretValue" type="string" required>
The new value of the secret.
</ParamField>
<ParamField query="ProjectId" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="Environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="Path" type="string" optional>
The path from where secret should be updated.
</ParamField>
<ParamField query="Type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
</ParamField>
</Expandable>
</ParamField>
### client.DeleteSecret(options)
```cs
var options = new DeleteSecretOptions
{
Environment = "dev",
ProjectId = "PROJECT_ID",
SecretName = "NEW_SECRET",
};
var deletedSecret = infisical.DeleteSecret(options);
```
Delete a secret in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
<ParamField query="SecretName" type="string">
The key of the secret to update.
</ParamField>
<ParamField query="ProjectId" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="Environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="Path" type="string" optional>
The path from where secret should be deleted.
</ParamField>
<ParamField query="Type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
</ParamField>
</Expandable>
</ParamField>
## Cryptography
### Create a symmetric key
Create a base64-encoded, 256-bit symmetric key to be used for encryption/decryption.
```cs
var key = infisical.CreateSymmetricKey();
```
#### Returns (string)
`key` (string): A base64-encoded, 256-bit symmetric key, that can be used for encryption/decryption purposes.
### Encrypt symmetric
```cs
var options = new EncryptSymmetricOptions
{
Plaintext = "Infisical is awesome!",
Key = key,
};
var encryptedData = infisical.EncryptSymmetric(options);
```
#### Parameters
<ParamField query="Parameters" type="object" required>
<Expandable title="properties">
<ParamField query="Plaintext" type="string">
The plaintext you want to encrypt.
</ParamField>
<ParamField query="Key" type="string" required>
The symmetric key to use for encryption.
</ParamField>
</Expandable>
</ParamField>
#### Returns (object)
`Tag` (string): A base64-encoded, 128-bit authentication tag.
`Iv` (string): A base64-encoded, 96-bit initialization vector.
`CipherText` (string): A base64-encoded, encrypted ciphertext.
### Decrypt symmetric
```cs
var decryptOptions = new DecryptSymmetricOptions
{
Key = key,
Ciphertext = encryptedData.Ciphertext,
Iv = encryptedData.Iv,
Tag = encryptedData.Tag,
};
var decryptedPlaintext = infisical.DecryptSymmetric(decryptOptions);
```
#### Parameters
<ParamField query="Parameters" type="object" required>
<Expandable title="properties">
<ParamField query="Ciphertext" type="string">
The ciphertext you want to decrypt.
</ParamField>
<ParamField query="Key" type="string" required>
The symmetric key to use for encryption.
</ParamField>
<ParamField query="Iv" type="string" required>
The initialization vector to use for decryption.
</ParamField>
<ParamField query="Tag" type="string" required>
The authentication tag to use for decryption.
</ParamField>
</Expandable>
</ParamField>
#### Returns (string)
`Plaintext` (string): The decrypted plaintext.

@ -1,8 +0,0 @@
---
title: "Go"
icon: "golang"
---
Coming soon.
Star our GitHub repository to stay updated [cross-language SDK](https://github.com/Infisical/sdk) GitHub repository to stay updated.

@ -1,10 +1,13 @@
---
title: "Java"
title: "Infisical Java SDK"
icon: "java"
---
If you're working with Java, the official [Infisical Java SDK](https://github.com/Infisical/sdk/tree/main/languages/java) package is the easiest way to fetch and work with secrets for your application.
- [Maven Package](https://github.com/Infisical/sdk/packages/2019741)
- [Github Repository](https://github.com/Infisical/sdk/tree/main/languages/java)
## Basic Usage
```java
@ -121,7 +124,7 @@ SecretElement[] secrets = client.listSecrets(options);
Retrieve all secrets within the Infisical project and environment that client is connected to
### Methods
#### Methods
<ParamField query="Parameters" type="object">
<Expandable title="properties">
@ -165,7 +168,7 @@ Retrieve a secret from Infisical.
By default, `getSecret()` fetches and returns a shared secret.
### Methods
#### Methods
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
@ -203,7 +206,7 @@ CreateSecretResponseSecret newSecret = client.createSecret(createOptions);
Create a new secret in Infisical.
### Methods
#### Methods
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
@ -245,7 +248,7 @@ UpdateSecretResponseSecret updatedSecret = client.updateSecret(options);
Update an existing secret in Infisical.
### Methods
#### Methods
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
@ -286,7 +289,7 @@ DeleteSecretResponseSecret deletedSecret = client.deleteSecret(options);
Delete a secret in Infisical.
### Methods
#### Methods
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
@ -307,3 +310,76 @@ Delete a secret in Infisical.
</ParamField>
</Expandable>
</ParamField>
## Cryptography
### Create a symmetric key
Create a base64-encoded, 256-bit symmetric key to be used for encryption/decryption.
```java
String key = client.createSymmetricKey();
```
#### Returns (string)
`key` (string): A base64-encoded, 256-bit symmetric key, that can be used for encryption/decryption purposes.
### Encrypt symmetric
```java
EncryptSymmetricOptions options = new EncryptSymmetricOptions();
options.setKey(key);
options.setPlaintext("Infisical is awesome!");
EncryptSymmetricResponse encryptedData = client.encryptSymmetric(options);
```
#### Methods
<ParamField query="Parameters" type="object" required>
<Expandable title="properties">
<ParamField query="setPlaintext()" type="string">
The plaintext you want to encrypt.
</ParamField>
<ParamField query="setKey()" type="string" required>
The symmetric key to use for encryption.
</ParamField>
</Expandable>
</ParamField>
#### Returns (object)
`tag (getTag())` (string): A base64-encoded, 128-bit authentication tag.
`iv (getIv())` (string): A base64-encoded, 96-bit initialization vector.
`ciphertext (getCipherText())` (string): A base64-encoded, encrypted ciphertext.
### Decrypt symmetric
```java
DecryptSymmetricOptions decryptOptions = new DecryptSymmetricOptions();
decryptOptions.setKey(key);
decryptOptions.setCiphertext(encryptedData.getCiphertext());
decryptOptions.setIv(encryptedData.getIv());
decryptOptions.setTag(encryptedData.getTag());
String decryptedString = client.decryptSymmetric(decryptOptions);
```
#### Methods
<ParamField query="Parameters" type="object" required>
<Expandable title="properties">
<ParamField query="setCiphertext()" type="string">
The ciphertext you want to decrypt.
</ParamField>
<ParamField query="setKey()" type="string" required>
The symmetric key to use for encryption.
</ParamField>
<ParamField query="setIv()" type="string" required>
The initialization vector to use for decryption.
</ParamField>
<ParamField query="setTag()" type="string" required>
The authentication tag to use for decryption.
</ParamField>
</Expandable>
</ParamField>
#### Returns (string)
`Plaintext` (string): The decrypted plaintext.

@ -1,10 +1,13 @@
---
title: "Node"
title: "Infisical Node.js SDK"
icon: "node"
---
If you're working with Node.js, the official [infisical-node](https://github.com/Infisical/sdk/tree/main/languages/node) package is the easiest way to fetch and work with secrets for your application.
- [NPM Package](https://www.npmjs.com/package/@infisical/sdk)
- [Github Repository](https://github.com/Infisical/sdk/tree/main/languages/node)
## Basic Usage
```js
@ -84,12 +87,12 @@ Import the SDK and create a client instance with your [Machine Identity](/docume
clientSecret: "YOUR_CLIENT_SECRET",
logLevel: LogLevel.Error
});
````
```
</Tab>
</Tabs>
### Parameters
#### Parameters
<ParamField query="options" type="object">
<Expandable title="properties">
@ -138,7 +141,7 @@ const secrets = await client.listSecrets({
Retrieve all secrets within the Infisical project and environment that client is connected to
### Parameters
#### Parameters
<ParamField query="Parameters" type="object">
<Expandable title="properties">
@ -180,7 +183,7 @@ Retrieve a secret from Infisical.
By default, `getSecret()` fetches and returns a shared secret.
### Parameters
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
@ -255,7 +258,7 @@ const updatedApiKey = await client.updateSecret({
Update an existing secret in Infisical.
### Parameters
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
@ -315,3 +318,73 @@ Delete a secret in Infisical.
</ParamField>
</Expandable>
</ParamField>
## Cryptography
### Create a symmetric key
Create a base64-encoded, 256-bit symmetric key to be used for encryption/decryption.
```js
const key = client.createSymmetricKey();
```
#### Returns (string)
`key` (string): A base64-encoded, 256-bit symmetric key, that can be used for encryption/decryption purposes.
### Encrypt symmetric
```js
const { iv, tag, ciphertext } = await client.encryptSymmetric({
key: key,
plaintext: "Infisical is awesome!",
})
```
#### Parameters
<ParamField query="Parameters" type="object" required>
<Expandable title="properties">
<ParamField query="plaintext" type="string">
The plaintext you want to encrypt.
</ParamField>
<ParamField query="key" type="string" required>
The symmetric key to use for encryption.
</ParamField>
</Expandable>
</ParamField>
#### Returns (object)
`tag` (string): A base64-encoded, 128-bit authentication tag.
`iv` (string): A base64-encoded, 96-bit initialization vector.
`ciphertext` (string): A base64-encoded, encrypted ciphertext.
### Decrypt symmetric
```js
const decryptedString = await client.decryptSymmetric({
key: key,
iv: iv,
tag: tag,
ciphertext: ciphertext,
});
```
#### Parameters
<ParamField query="Parameters" type="object" required>
<Expandable title="properties">
<ParamField query="ciphertext" type="string">
The ciphertext you want to decrypt.
</ParamField>
<ParamField query="key" type="string" required>
The symmetric key to use for encryption.
</ParamField>
<ParamField query="iv" type="string" required>
The initialization vector to use for decryption.
</ParamField>
<ParamField query="tag" type="string" required>
The authentication tag to use for decryption.
</ParamField>
</Expandable>
</ParamField>
#### Returns (string)
`plaintext` (string): The decrypted plaintext.

@ -1,8 +0,0 @@
---
title: "PHP"
icon: "php"
---
Coming soon.
Star our GitHub repository to stay updated [cross-language SDK](https://github.com/Infisical/sdk) GitHub repository to stay updated.

@ -1,10 +1,13 @@
---
title: "Python"
title: "Infisical Python SDK"
icon: "python"
---
If you're working with Python, the official [infisical-python](https://github.com/Infisical/sdk/edit/main/crates/infisical-py) package is the easiest way to fetch and work with secrets for your application.
- [PyPi Package](https://pypi.org/project/infisical-python/)
- [Github Repository](https://github.com/Infisical/sdk/edit/main/crates/infisical-py)
## Basic Usage
```py
@ -60,7 +63,7 @@ client = InfisicalClient(ClientSettings(
))
```
### Parameters
#### Parameters
<ParamField query="options" type="object">
<Expandable title="properties">
@ -110,7 +113,7 @@ client.listSecrets(options=ListSecretsOptions(
Retrieve all secrets within the Infisical project and environment that client is connected to
### Parameters
#### Parameters
<ParamField query="Parameters" type="object">
<Expandable title="properties">
@ -149,7 +152,7 @@ value = secret.secret_value # get its value
By default, `getSecret()` fetches and returns a shared secret. If not found, it returns a personal secret.
### Parameters
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
@ -187,7 +190,7 @@ api_key = client.createSecret(options=CreateSecretOptions(
Create a new secret in Infisical.
### Parameters
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
@ -225,7 +228,7 @@ client.updateSecret(options=UpdateSecretOptions(
Update an existing secret in Infisical.
### Parameters
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
@ -262,7 +265,7 @@ client.deleteSecret(options=DeleteSecretOptions(
Delete a secret in Infisical.
### Parameters
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
@ -283,3 +286,79 @@ Delete a secret in Infisical.
</ParamField>
</Expandable>
</ParamField>
## Cryptography
### Create a symmetric key
Create a base64-encoded, 256-bit symmetric key to be used for encryption/decryption.
```py
key = client.createSymmetricKey()
```
#### Returns (string)
`key` (string): A base64-encoded, 256-bit symmetric key, that can be used for encryption/decryption purposes.
### Encrypt symmetric
```py
encryptOptions = EncryptSymmetricOptions(
key=key,
plaintext="Infisical is awesome!"
)
encryptedData = client.encryptSymmetric(encryptOptions)
```
#### Parameters
<ParamField query="Parameters" type="object" required>
<Expandable title="properties">
<ParamField query="plaintext" type="string">
The plaintext you want to encrypt.
</ParamField>
<ParamField query="key" type="string" required>
The symmetric key to use for encryption.
</ParamField>
</Expandable>
</ParamField>
#### Returns (object)
`tag` (string): A base64-encoded, 128-bit authentication tag.
`iv` (string): A base64-encoded, 96-bit initialization vector.
`ciphertext` (string): A base64-encoded, encrypted ciphertext.
### Decrypt symmetric
```py
decryptOptions = DecryptSymmetricOptions(
ciphertext=encryptedData.ciphertext,
iv=encryptedData.iv,
tag=encryptedData.tag,
key=key
)
decryptedString = client.decryptSymmetric(decryptOptions)
```
#### Parameters
<ParamField query="Parameters" type="object" required>
<Expandable title="properties">
<ParamField query="ciphertext" type="string">
The ciphertext you want to decrypt.
</ParamField>
<ParamField query="key" type="string" required>
The symmetric key to use for encryption.
</ParamField>
<ParamField query="iv" type="string" required>
The initialization vector to use for decryption.
</ParamField>
<ParamField query="tag" type="string" required>
The authentication tag to use for decryption.
</ParamField>
</Expandable>
</ParamField>
#### Returns (string)
`plaintext` (string): The decrypted plaintext.

@ -1,8 +0,0 @@
---
title: "Ruby"
icon: "gem"
---
Coming soon.
Star our GitHub repository to stay updated [cross-language SDK](https://github.com/Infisical/sdk) GitHub repository to stay updated.

@ -1,8 +0,0 @@
---
title: "Rust"
icon: "rust"
---
Coming soon.
Star our GitHub repository to stay updated [cross-language SDK](https://github.com/Infisical/sdk) GitHub repository to stay updated.

@ -18,17 +18,8 @@ From local development to production, Infisical SDKs provide the easiest way for
<Card href="/sdks/languages/java" title="Java" icon="java" color="#e41f23">
Manage secrets for your Java application on demand
</Card>
<Card href="/sdks/languages/ruby" title="Ruby" icon="gem" color="#ac0d01">
Manage secrets for your Ruby application on demand
</Card>
<Card href="/sdks/languages/go" title="Golang" icon="golang" color="#00add8">
Manage secrets for your Go application on demand
</Card>
<Card href="/sdks/languages/rust" title="Rust" icon="rust" color="#cd412b">
Manage secrets for your Rust application on demand
</Card>
<Card href="/sdks/languages/php" title="PHP" icon="php" color="#787cb4">
Manage secrets for your PHP application on demand
<Card href="/sdks/languages/csharp" title="C#" icon="bars" color="#368833">
Manage secrets for your C#/.NET application on demand
</Card>
</CardGroup>
@ -41,6 +32,10 @@ From local development to production, Infisical SDKs provide the easiest way for
Note: The exact parameter name may differ depending on the language.
</Accordion>
<Accordion title="What if a request for a secret fails?">
The SDK caches every secret and falls back to the cached value if a request fails. If no cached
value ever-existed, the SDK falls back to whatever value is on the process environment.
</Accordion>
<Accordion title="Can I attach the environment variables to my process environment?">
Yes you can! The client SDK provides a method to attach the secrets to your process environment. When using the `listSecrets()` method, you
can pass a `attachToProcessEnv` parameter, which tells the SDK to attach all the found secrets to your process environment.

@ -3133,6 +3133,7 @@ paths:
description: Projects of organization
security:
- apiKeyAuth: []
bearerAuth: []
/api/v2/organizations/:
post:
description: ''

Binary file not shown.

After

(image error) Size: 78 KiB

@ -49,7 +49,7 @@ export default function DonwloadBackupPDFStep({
console.log(err);
createNotification({
type: "error",
text: "Faield to generate backup key"
text: "Failed to generate backup key"
});
} finally {
setIsLoading.off();

@ -58,7 +58,10 @@ const Alert = forwardRef<
{typeof icon !== "undefined" ? (
<>{icon} </>
) : (
<FontAwesomeIcon className="text-lg" icon={variantIconMap[variant ?? "default"]} />
<FontAwesomeIcon
className="text-lg text-primary"
icon={variantIconMap[variant ?? "default"]}
/>
)}
</div>
<div className="flex flex-col gap-y-1">

@ -1,6 +1,7 @@
export {
useAuthorizeIntegration,
useDeleteIntegrationAuth,
useDeleteIntegrationAuths,
useGetIntegrationAuthApps,
useGetIntegrationAuthBitBucketWorkspaces,
useGetIntegrationAuthById,

@ -297,6 +297,7 @@ const fetchIntegrationAuthRailwayEnvironments = async ({
integrationAuthId: string;
appId: string;
}) => {
if (appId === "none") return [];
const {
data: { environments }
} = await apiRequest.get<{ environments: Environment[] }>(
@ -318,6 +319,7 @@ const fetchIntegrationAuthRailwayServices = async ({
integrationAuthId: string;
appId: string;
}) => {
if (appId === "none") return [];
const {
data: { services }
} = await apiRequest.get<{ services: Service[] }>(
@ -697,7 +699,22 @@ export const useSaveIntegrationAccessToken = () => {
});
};
export const useDeleteIntegrationAuth = () => {
export const useDeleteIntegrationAuths = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, { integration: string; workspaceId: string }>({
mutationFn: ({ integration, workspaceId }) => apiRequest.delete(`/api/v1/integration-auth?${new URLSearchParams({
integration,
workspaceId
})}`),
onSuccess: (_, { workspaceId }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceAuthorization(workspaceId));
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIntegrations(workspaceId));
}
});
};
export const useDeleteIntegrationAuth = () => { // not used
const queryClient = useQueryClient();
return useMutation<{}, {}, { id: string; workspaceId: string }>({

@ -96,6 +96,7 @@ export const useDeleteIntegration = () => {
mutationFn: ({ id }) => apiRequest.delete(`/api/v1/integration/${id}`),
onSuccess: (_, { workspaceId }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIntegrations(workspaceId));
queryClient.invalidateQueries(workspaceKeys.getWorkspaceAuthorization(workspaceId));
}
});
};

@ -126,6 +126,7 @@ const fetchWorkspaceAuthorization = async (workspaceId: string) => {
const { data } = await apiRequest.get<{ authorizations: IntegrationAuth[] }>(
`/api/v1/workspace/${workspaceId}/authorizations`
);
return data.authorizations;
};

@ -70,7 +70,7 @@ export const AdminLayout = ({ children }: LayoutProps) => {
const { user } = useUser();
const { subscription } = useSubscription();
const { data: updateClosed } = useGetUserAction("september_update_closed");
const { data: updateClosed } = useGetUserAction("december_update_closed");
const infisicalPlatformVersion = process.env.NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION;
const { t } = useTranslation();
@ -78,7 +78,7 @@ export const AdminLayout = ({ children }: LayoutProps) => {
const registerUserAction = useRegisterUserAction();
const closeUpdate = async () => {
await registerUserAction.mutateAsync("september_update_closed");
await registerUserAction.mutateAsync("december_update_closed");
};
const logout = useLogoutUser();
@ -182,14 +182,14 @@ export const AdminLayout = ({ children }: LayoutProps) => {
} relative z-10 mb-6 flex pb-2 w-52 flex-col items-center justify-start rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3`}
>
<div className="text-md mt-2 w-full font-semibold text-mineshaft-100">
Infisical September update
Infisical December update
</div>
<div className="mt-1 mb-1 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300">
Improved RBAC, new integrations, dashboard remake, and more!
Infisical Agent, new SDKs, Machine Identities, and more!
</div>
<div className="mt-2 h-[6.77rem] w-full rounded-md border border-mineshaft-700">
<Image
src="/images/infisical-update-september-2023.png"
src="/images/infisical-update-december-2023.png"
height={319}
width={539}
alt="kubernetes image"
@ -205,7 +205,7 @@ export const AdminLayout = ({ children }: LayoutProps) => {
Close
</button>
<a
href="https://infisical.com/blog/infisical-update-september-2023"
href="https://infisical.com/blog/infisical-update-december-2023"
target="_blank"
rel="noopener noreferrer"
className="text-sm font-normal leading-[1.2rem] text-mineshaft-400 duration-200 hover:text-mineshaft-100"

@ -121,7 +121,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
const { user } = useUser();
const { subscription } = useSubscription();
const workspaceId = currentWorkspace?._id || "";
const { data: updateClosed } = useGetUserAction("september_update_closed");
const { data: updateClosed } = useGetUserAction("december_update_closed");
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId });
@ -153,7 +153,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
const registerUserAction = useRegisterUserAction();
const closeUpdate = async () => {
await registerUserAction.mutateAsync("september_update_closed");
await registerUserAction.mutateAsync("december_update_closed");
};
const logout = useLogoutUser();
@ -646,14 +646,14 @@ export const AppLayout = ({ children }: LayoutProps) => {
} relative z-10 mb-6 flex h-64 w-52 flex-col items-center justify-start rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3`}
>
<div className="text-md mt-2 w-full font-semibold text-mineshaft-100">
Infisical September update
Infisical December update
</div>
<div className="mt-1 mb-1 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300">
Improved RBAC, new integrations, dashboard remake, and more!
Infisical Agent, new SDKs, Machine Identities, and more!
</div>
<div className="mt-2 h-[6.77rem] w-full rounded-md border border-mineshaft-700">
<Image
src="/images/infisical-update-september-2023.png"
src="/images/infisical-update-december-2023.png"
height={319}
width={539}
alt="kubernetes image"
@ -669,7 +669,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
Close
</button>
<a
href="https://infisical.com/blog/infisical-update-september-2023"
href="https://infisical.com/blog/infisical-update-december-2023"
target="_blank"
rel="noopener noreferrer"
className="text-sm font-normal leading-[1.2rem] text-mineshaft-400 duration-200 hover:text-mineshaft-100"

@ -76,10 +76,15 @@ export default function RailwayCreateIntegrationPage() {
}
}, [targetEnvironments]);
const filteredServices = targetServices?.concat({
name: "",
serviceId: ""
});
useEffect(() => {
if (targetServices) {
if (targetServices.length > 0) {
setTargetServiceId(targetServices[0].serviceId);
} else {
setTargetServiceId("none");
}
}
}, [targetServices]);
const handleButtonClick = async () => {
try {
@ -125,7 +130,7 @@ export default function RailwayCreateIntegrationPage() {
selectedSourceEnvironment &&
integrationAuthApps &&
targetEnvironments &&
filteredServices ? (
targetServices ? (
<div className="flex h-full w-full items-center justify-center">
<Card className="max-w-md rounded-md p-8">
<CardTitle className="text-center">Railway Integration</CardTitle>
@ -180,6 +185,7 @@ export default function RailwayCreateIntegrationPage() {
value={targetEnvironmentId}
onValueChange={(val) => setTargetEnvironmentId(val)}
className="w-full border border-mineshaft-500"
isDisabled={targetEnvironments.length === 0}
>
{targetEnvironments.length > 0 ? (
targetEnvironments.map((targetEnvironment) => (
@ -202,15 +208,22 @@ export default function RailwayCreateIntegrationPage() {
value={targetServiceId}
onValueChange={(val) => setTargetServiceId(val)}
className="w-full border border-mineshaft-500"
isDisabled={targetServices.length === 0}
>
{filteredServices.map((targetService) => (
<SelectItem
value={targetService.serviceId as string}
key={`target-service-${targetService.serviceId as string}`}
>
{targetService.name}
{targetServices.length > 0 ? (
targetServices.map((targetService) => (
<SelectItem
value={targetService.serviceId as string}
key={`target-service-${targetService.serviceId as string}`}
>
{targetService.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-service-none">
No services found
</SelectItem>
))}
)}
</Select>
</FormControl>
<Button

@ -56,7 +56,7 @@ export default function VercelCreateIntegrationPage() {
appId: targetAppId
});
const filteredBranches = branches?.filter((branchName) => branchName !== "main").concat("");
const filteredBranches = branches?.filter((branchName) => branchName !== "main").concat();
useEffect(() => {
if (workspace) {
@ -125,7 +125,7 @@ export default function VercelCreateIntegrationPage() {
subTitle="Select which environment or folder in Infisical you want to sync to Vercel's environment variables."
>
<div className="flex flex-row items-center">
<div className="inline flex items-center">
<div className="flex items-center">
<Image
src="/images/integrations/Vercel.png"
height={30}

@ -23,7 +23,9 @@ import {
faNetworkWired,
faPlug,
faPlus,
faUserPlus
faUserPlus,
faWarning,
faXmark
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup";
@ -56,6 +58,7 @@ import {
fetchOrgUsers,
useAddUserToWs,
useCreateWorkspace,
useGetUserAction,
useRegisterUserAction,
useUploadWsKey
} from "@app/hooks/api";
@ -70,13 +73,12 @@ const features = [
link: "https://infisical.com/docs/documentation/getting-started/kubernetes",
description:
"Pull secrets into your Kubernetes containers and automatically redeploy upon secret changes."
},
},
{
_id: 1,
name: "Infisical Agent",
link: "https://infisical.com/docs/infisical-agent/overview",
description:
"Inject secrets into your apps without modifying any application logic."
description: "Inject secrets into your apps without modifying any application logic."
}
];
@ -122,13 +124,13 @@ const CodeItem = ({
}) => {
return (
<>
<p className="mb-2 mt-4 text-bunker-300 text-sm leading-normal">{textExplanation}</p>
<div className="font-mono text-sm px-3 py-2 bg-bunker rounded-md border border-mineshaft-600 flex flex-row items-center justify-between">
<p className="mb-2 mt-4 text-sm leading-normal text-bunker-300">{textExplanation}</p>
<div className="flex flex-row items-center justify-between rounded-md border border-mineshaft-600 bg-bunker px-3 py-2 font-mono text-sm">
<input disabled value={code} id={id} className="w-full bg-transparent text-bunker-200" />
<button
type="button"
onClick={() => copyToClipboard(id, setIsCopied)}
className="h-full pl-3.5 pr-2 text-bunker-300 hover:text-primary-200 duration-200"
className="h-full pl-3.5 pr-2 text-bunker-300 duration-200 hover:text-primary-200"
>
{isCopied ? (
<FontAwesomeIcon icon={faCheck} className="pr-0.5" />
@ -150,21 +152,21 @@ const TabsObject = () => {
return (
<Tabs.Root
className="flex flex-col w-full cursor-default border border-mineshaft-600 rounded-md"
className="flex w-full cursor-default flex-col rounded-md border border-mineshaft-600"
defaultValue="tab1"
>
<Tabs.List
className="shrink-0 flex border-b border-mineshaft-600"
className="flex shrink-0 border-b border-mineshaft-600"
aria-label="Manage your account"
>
<Tabs.Trigger
className="bg-bunker-700 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
className="flex h-10 flex-1 cursor-default select-none items-center justify-center bg-bunker-700 px-5 text-sm leading-none text-bunker-300 outline-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:border-b data-[state=active]:border-primary data-[state=active]:font-medium data-[state=active]:text-primary data-[state=active]:focus:relative"
value="tab1"
>
MacOS
</Tabs.Trigger>
<Tabs.Trigger
className="bg-bunker-700 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
className="flex h-10 flex-1 cursor-default select-none items-center justify-center bg-bunker-700 px-5 text-sm leading-none text-bunker-300 outline-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:border-b data-[state=active]:border-primary data-[state=active]:font-medium data-[state=active]:text-primary data-[state=active]:focus:relative"
value="tab2"
>
Windows
@ -178,14 +180,14 @@ const TabsObject = () => {
<a
target="_blank"
rel="noopener noreferrer"
className="bg-bunker-700 hover:text-bunker-100 duration-200 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
className="flex h-10 flex-1 cursor-default select-none items-center justify-center bg-bunker-700 px-5 text-sm leading-none text-bunker-300 outline-none duration-200 first:rounded-tl-md last:rounded-tr-md hover:text-bunker-100 data-[state=active]:border-b data-[state=active]:border-primary data-[state=active]:font-medium data-[state=active]:text-primary data-[state=active]:focus:relative"
href="https://infisical.com/docs/cli/overview"
>
Other Platforms <FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-2" />
</a>
</Tabs.List>
<Tabs.Content
className="grow p-5 pt-0 bg-bunker-700 rounded-b-md outline-none cursor-default"
className="grow cursor-default rounded-b-md bg-bunker-700 p-5 pt-0 outline-none"
value="tab1"
>
<CodeItem
@ -216,7 +218,7 @@ const TabsObject = () => {
code="infisical run -- [YOUR USUAL CODE START SCRIPT GOES HERE]"
id="runCode"
/>
<p className="text-bunker-300 text-sm mt-2">
<p className="mt-2 text-sm text-bunker-300">
You can find example of start commands for different frameworks{" "}
<a
className="text-primary underline underline-offset-2"
@ -229,7 +231,7 @@ const TabsObject = () => {
.{" "}
</p>
</Tabs.Content>
<Tabs.Content className="grow p-5 pt-0 bg-bunker-700 rounded-b-md outline-none" value="tab2">
<Tabs.Content className="grow rounded-b-md bg-bunker-700 p-5 pt-0 outline-none" value="tab2">
<CodeItem
isCopied={downloadCodeCopied}
setIsCopied={setDownloadCodeCopied}
@ -237,7 +239,7 @@ const TabsObject = () => {
code="scoop bucket add org https://github.com/Infisical/scoop-infisical.git"
id="downloadCodeW"
/>
<div className="font-mono text-sm px-3 py-2 mt-2 bg-bunker rounded-md border border-mineshaft-600 flex flex-row items-center justify-between">
<div className="mt-2 flex flex-row items-center justify-between rounded-md border border-mineshaft-600 bg-bunker px-3 py-2 font-mono text-sm">
<input
disabled
value="scoop install infisical"
@ -247,7 +249,7 @@ const TabsObject = () => {
<button
type="button"
onClick={() => copyToClipboard("downloadCodeW2", setDownloadCode2Copied)}
className="h-full pl-3.5 pr-2 text-bunker-300 hover:text-primary-200 duration-200"
className="h-full pl-3.5 pr-2 text-bunker-300 duration-200 hover:text-primary-200"
>
{downloadCode2Copied ? (
<FontAwesomeIcon icon={faCheck} className="pr-0.5" />
@ -277,7 +279,7 @@ const TabsObject = () => {
code="infisical run -- [YOUR USUAL CODE START SCRIPT GOES HERE]"
id="runCodeW"
/>
<p className="text-bunker-300 text-sm mt-2">
<p className="mt-2 text-sm text-bunker-300">
You can find example of start commands for different frameworks{" "}
<a
className="text-primary underline underline-offset-2"
@ -481,6 +483,13 @@ const OrganizationPage = withPermission(
const { createNotification } = useNotificationContext();
const addWsUser = useAddUserToWs();
const { data: updateClosed } = useGetUserAction("jan_2024_db_update_closed");
const registerUserAction = useRegisterUserAction();
const closeUpdate = async () => {
await registerUserAction.mutateAsync("jan_2024_db_update_closed");
};
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"addNewWs",
"upgradePlan"
@ -586,10 +595,10 @@ const OrganizationPage = withPermission(
{!serverDetails?.redisConfigured && (
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
<p className="mr-4 mb-4 font-semibold text-white">Announcements</p>
<div className="w-full border border-blue-400/70 rounded-md bg-blue-900/70 p-2 text-base text-mineshaft-100 flex items-center">
<div className="flex w-full items-center rounded-md border border-blue-400/70 bg-blue-900/70 p-2 text-base text-mineshaft-100">
<FontAwesomeIcon
icon={faExclamationCircle}
className="text-2xl mr-4 p-4 text-mineshaft-50"
className="mr-4 p-4 text-2xl text-mineshaft-50"
/>
Attention: Updated versions of Infisical now require Redis for full functionality.
Learn how to configure it
@ -597,7 +606,7 @@ const OrganizationPage = withPermission(
href="https://infisical.com/docs/self-hosting/configuration/redis"
target="_blank"
>
<span className="pl-1 text-white underline underline-offset-2 hover:decoration-blue-400 hover:text-blue-200 duration-100 cursor-pointer">
<span className="cursor-pointer pl-1 text-white underline underline-offset-2 duration-100 hover:text-blue-200 hover:decoration-blue-400">
here
</span>
</Link>
@ -606,6 +615,22 @@ const OrganizationPage = withPermission(
</div>
)}
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
<div className={`${
!updateClosed ? "block" : "hidden"
} mb-4 w-full border rounded-md p-2 text-base border-primary-600 bg-primary/10 text-white flex flex-row items-center`}>
<FontAwesomeIcon icon={faWarning} className="text-primary text-4xl p-6"/>
<div className="text-sm">
<span className="text-lg font-semibold">Scheduled maintenance on January 27th</span> <br />
We&apos;ve planned a database upgrade and need to pause certain functionality for approximately 3 hours on Saturday, January 27th, 10am EST. During these hours, read operations will continue to function normally but no resources will be editable. No action is required on your end your applications can continue to fetch secrets.<br />
</div>
<button
type="button"
onClick={() => closeUpdate()}
className="text-mineshaft-100 duration-200 hover:text-red-400 h-full flex items-start"
>
<FontAwesomeIcon icon={faXmark} />
</button>
</div>
<p className="mr-4 font-semibold text-white">Projects</p>
<div className="mt-6 flex w-full flex-row">
<Input
@ -701,7 +726,7 @@ const OrganizationPage = withPermission(
</div>
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-6 text-3xl">
<p className="mr-4 font-semibold text-white">Explore Infisical</p>
<div className="mt-4 grid grid-cols-3 w-full gap-4">
<div className="mt-4 grid w-full grid-cols-3 gap-4">
{features.map((feature) => (
<div
key={feature._id}

@ -1,6 +1,5 @@
import { useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useRouter } from "next/router";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { Button, Modal, ModalContent } from "@app/components/v2";
@ -9,7 +8,7 @@ import { withProjectPermission } from "@app/hoc";
import { usePopUp } from "@app/hooks";
import {
useDeleteIntegration,
useDeleteIntegrationAuth,
useDeleteIntegrationAuths,
useGetCloudIntegrations,
useGetUserWsKey,
useGetWorkspaceAuthorizations,
@ -24,8 +23,7 @@ import { FrameworkIntegrationSection } from "./components/FrameworkIntegrationSe
import { IntegrationsSection } from "./components/IntegrationsSection";
import {
generateBotKey,
redirectForProviderAuth,
redirectToIntegrationAppConfigScreen
redirectForProviderAuth
} from "./IntegrationPage.utils";
type Props = {
@ -36,7 +34,6 @@ export const IntegrationsPage = withProjectPermission(
({ frameworkIntegrations }: Props) => {
const { t } = useTranslation();
const { createNotification } = useNotificationContext();
const router = useRouter();
const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?._id || "";
@ -65,6 +62,7 @@ export const IntegrationsPage = withProjectPermission(
return groupBy;
}, [])
);
// mutation
const {
data: integrations,
@ -78,11 +76,11 @@ export const IntegrationsPage = withProjectPermission(
const { mutateAsync: updateBotActiveStatus, mutate: updateBotActiveStatusSync } =
useUpdateBotActiveStatus();
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
const {
mutateAsync: deleteIntegrationAuth,
const {
mutateAsync: deleteIntegrationAuths,
isSuccess: isDeleteIntegrationAuthSuccess,
reset: resetDeleteIntegrationAuth
} = useDeleteIntegrationAuth();
reset: resetDeleteIntegrationAuths
} = useDeleteIntegrationAuths();
const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).length;
const isIntegrationsEmpty = !integrations?.length;
@ -103,7 +101,7 @@ export const IntegrationsPage = withProjectPermission(
botId: bot._id,
workspaceId
});
resetDeleteIntegrationAuth();
resetDeleteIntegrationAuths();
}
}, [
isIntegrationFetching,
@ -116,7 +114,7 @@ export const IntegrationsPage = withProjectPermission(
const handleProviderIntegration = async (provider: string) => {
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
if (!selectedCloudIntegration) return;
try {
if (bot && !bot.isActive) {
const botKey = generateBotKey(bot.publicKey, latestWsKey!);
@ -127,14 +125,8 @@ export const IntegrationsPage = withProjectPermission(
botId: bot._id
});
}
const integrationAuthForProvider = integrationAuths?.[provider];
if (!integrationAuthForProvider) {
redirectForProviderAuth(selectedCloudIntegration);
return;
}
const url = redirectToIntegrationAppConfigScreen(provider, integrationAuthForProvider._id);
router.push(url);
redirectForProviderAuth(selectedCloudIntegration);
} catch (error) {
console.error(error);
}
@ -176,9 +168,10 @@ export const IntegrationsPage = withProjectPermission(
const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => {
const integrationAuthForProvider = integrationAuths?.[provider];
if (!integrationAuthForProvider) return;
try {
await deleteIntegrationAuth({
id: integrationAuthForProvider._id,
await deleteIntegrationAuths({
integration: provider,
workspaceId
});
if (cb) cb();

@ -72,9 +72,9 @@ export const IdentitySection = withPermission(
</p>
<div className="flex justify-end w-full pr-4">
<Link href="https://infisical.com/docs/documentation/platform/identities/overview">
<span className="rounded-md px-4 py-2 w-max text-mineshaft-200 hover:text-white bg-mineshaft-600 border border-mineshaft-500 hover:bg-primary/10 hover:border-primary/40 duration-200 cursor-pointer">
<a target="_blank" rel="noopener noreferrer" className="rounded-md px-4 py-2 w-max text-mineshaft-200 hover:text-white bg-mineshaft-600 border border-mineshaft-500 hover:bg-primary/10 hover:border-primary/40 duration-200 cursor-pointer">
Documentation <FontAwesomeIcon icon={faArrowUpRightFromSquare} className="text-xs mb-[0.06rem] ml-1"/>
</span>
</a>
</Link>
</div>
<OrgPermissionCan

@ -98,6 +98,7 @@ export const IdentityTable = ({
<THead>
<Tr>
<Th>Name</Th>
<Th>ID</Th>
<Th>Role</Th>
<Th>Auth Method</Th>
<Th className="w-5" />
@ -120,6 +121,7 @@ export const IdentityTable = ({
return (
<Tr className="h-10" key={`identity-${_id}`}>
<Td>{name}</Td>
<Td>{_id}</Td>
<Td>
<OrgPermissionCan
I={OrgPermissionActions.Edit}

@ -91,12 +91,14 @@ export const IdentityUniversalAuthForm = ({
accessTokenTTL: "2592000",
accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0",
clientSecretTrustedIps: [{
ipAddress: "0.0.0.0/0"
}],
accessTokenTrustedIps: [{
ipAddress: "0.0.0.0/0"
}],
clientSecretTrustedIps: [
{ ipAddress: "0.0.0.0/0" },
{ ipAddress: "::/0" }
],
accessTokenTrustedIps: [
{ ipAddress: "0.0.0.0/0" },
{ ipAddress: "::/0" }
],
}
});
@ -139,12 +141,14 @@ export const IdentityUniversalAuthForm = ({
accessTokenTTL: "2592000",
accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0",
clientSecretTrustedIps: [{
ipAddress: "0.0.0.0/0"
}],
accessTokenTrustedIps: [{
ipAddress: "0.0.0.0/0"
}]
clientSecretTrustedIps: [
{ ipAddress: "0.0.0.0/0" },
{ ipAddress: "::/0" }
],
accessTokenTrustedIps: [
{ ipAddress: "0.0.0.0/0" },
{ ipAddress: "::/0" }
]
});
}
}, [data]);

@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
import { faCheck, faCopy, faPlus, faTrashCan } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup";
import { AxiosError } from "axios";
import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
@ -160,10 +161,18 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => {
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to create a service token",
type: "error"
});
const axiosError = err as AxiosError
if (axiosError?.response?.status === 401) {
createNotification({
text: "You do not have access to the selected environment/path",
type: "error"
});
} else {
createNotification({
text: "Failed to create a service token",
type: "error"
});
}
}
};

@ -1,5 +1,6 @@
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { AxiosError } from "axios";
import { z } from "zod";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
@ -78,12 +79,20 @@ export const CreateSecretImportForm = ({
type: "success",
text: "Successfully linked"
});
} catch (error) {
console.log(error);
createNotification({
type: "error",
text: "Failed to link secrets"
});
} catch (err) {
console.error(err);
const axiosError = err as AxiosError
if (axiosError?.response?.status === 401) {
createNotification({
text: "You do not have access to the selected environment/path",
type: "error"
});
} else {
createNotification({
type: "error",
text: "Failed to link secrets"
});
}
}
};

@ -89,7 +89,7 @@ export const SignUpPage = () => {
console.log(err);
createNotification({
type: "error",
text: "Faield to create admin"
text: "Failed to create admin"
});
}
};
@ -108,7 +108,7 @@ export const SignUpPage = () => {
console.log(err);
createNotification({
type: "error",
text: "Faield to generate backup"
text: "Failed to generate backup"
});
}
};