mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-23 03:03:05 +00:00
Compare commits
37 Commits
infisical/
...
update-blo
Author | SHA1 | Date | |
---|---|---|---|
8fbc930012 | |||
0e5190a920 | |||
b815e3eb56 | |||
31231cfcca | |||
ee772e4a77 | |||
7bc29c5981 | |||
e9a89930da | |||
b85499859c | |||
7f17194c0f | |||
1e1ad450d2 | |||
5287b322d8 | |||
45d96be1ff | |||
12840bfdbd | |||
fef5369738 | |||
c94b7d63f6 | |||
485ddc5c50 | |||
edd9c66e49 | |||
0a3b85534b | |||
ec2cc5162e | |||
7ce472957c | |||
8529e0da3d | |||
e5a5433f10 | |||
ee6e518ff8 | |||
15a7222505 | |||
25d482cc62 | |||
785a2bec6a | |||
449466f326 | |||
4131e9c3f1 | |||
310595256f | |||
1737880e58 | |||
b72483f5f2 | |||
ee14bda706 | |||
e56463d52b | |||
ebd3d7c7c4 | |||
9ecbfe201b | |||
ade27ad072 | |||
c7b2489d0b |
@ -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:
|
||||
|
||||
|
30
backend/package-lock.json
generated
30
backend/package-lock.json
generated
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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({
|
||||
|
@ -611,42 +611,81 @@ 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();
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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()
|
||||
|
@ -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,38 @@ 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 configPath != "" {
|
||||
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 +542,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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
---
|
@ -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
|
408
docs/contributing/sdk/developing.mdx
Normal file
408
docs/contributing/sdk/developing.mdx
Normal file
@ -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.
|
BIN
docs/images/sdk-flow.png
Normal file
BIN
docs/images/sdk-flow.png
Normal file
Binary file not shown.
After Width: | Height: | 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
@ -3133,6 +3133,7 @@ paths:
|
||||
description: Projects of organization
|
||||
security:
|
||||
- apiKeyAuth: []
|
||||
bearerAuth: []
|
||||
/api/v2/organizations/:
|
||||
post:
|
||||
description: ''
|
||||
|
BIN
frontend/public/images/infisical-update-december-2023.png
Normal file
BIN
frontend/public/images/infisical-update-december-2023.png
Normal file
Binary file not shown.
After Width: | Height: | 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();
|
||||
|
@ -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}
|
||||
|
@ -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]);
|
||||
|
@ -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"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user