diff --git a/backend/src/helpers/membershipOrg.ts b/backend/src/helpers/membershipOrg.ts
index 65c1e01c2..efe95e2c7 100644
--- a/backend/src/helpers/membershipOrg.ts
+++ b/backend/src/helpers/membershipOrg.ts
@@ -22,8 +22,8 @@ const validateMembershipOrg = async ({
}: {
userId: Types.ObjectId;
organizationId: Types.ObjectId;
- acceptedRoles: string[];
- acceptedStatuses: string[];
+ acceptedRoles: Array<'owner' | 'admin' | 'member'>;
+ acceptedStatuses: Array<'invited' | 'accepted'>;
}) => {
const membershipOrg = await MembershipOrg.findOne({
user: userId,
diff --git a/backend/src/helpers/organization.ts b/backend/src/helpers/organization.ts
index 0784f446b..10788cd8f 100644
--- a/backend/src/helpers/organization.ts
+++ b/backend/src/helpers/organization.ts
@@ -15,7 +15,8 @@ import {
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
- AUTH_MODE_API_KEY
+ AUTH_MODE_API_KEY,
+ OWNER
} from '../variables';
import {
getStripeSecretKey,
@@ -24,8 +25,15 @@ import {
getStripeProductStarter
} from '../config';
import {
- UnauthorizedRequestError
+ UnauthorizedRequestError,
+ OrganizationNotFoundError
} from '../utils/errors';
+import {
+ validateUserClientForOrganization
+} from '../helpers/user';
+import {
+ validateServiceAccountClientForOrganization
+} from '../helpers/serviceAccount';
/**
* Validate accepted clients for organization with id [organizationId]
@@ -35,30 +43,62 @@ import {
*/
const validateClientForOrganization = async ({
authData,
- organizationId
+ organizationId,
+ acceptedRoles,
+ acceptedStatuses
}: {
authData: {
authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData;
},
- organizationId: string;
+ organizationId: Types.ObjectId;
+ acceptedRoles: Array<'owner' | 'admin' | 'member'>;
+ acceptedStatuses: Array<'invited' | 'accepted'>;
}) => {
- // TODO
+
+ const organization = await Organization.findById(organizationId);
+
+ if (!organization) {
+ throw OrganizationNotFoundError({
+ message: 'Failed to find organization'
+ });
+ }
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
- // TODO
+ const membershipOrg = await validateUserClientForOrganization({
+ user: authData.authPayload,
+ organization,
+ acceptedRoles,
+ acceptedStatuses
+ });
+
+ return ({ organization, membershipOrg });
}
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
- // TODO
+ await validateServiceAccountClientForOrganization({
+ serviceAccount: authData.authPayload,
+ organization
+ });
+
+ return ({ organization });
}
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
- // TODO
+ throw UnauthorizedRequestError({
+ message: 'Failed service token authorization for organization resource'
+ });
}
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
- // TODO
+ const membershipOrg = await validateUserClientForOrganization({
+ user: authData.authPayload,
+ organization,
+ acceptedRoles,
+ acceptedStatuses
+ });
+
+ return ({ organization, membershipOrg });
}
throw UnauthorizedRequestError({
@@ -228,6 +268,7 @@ const updateSubscriptionOrgQuantity = async ({
};
export {
+ validateClientForOrganization,
createOrganization,
initSubscriptionOrg,
updateSubscriptionOrgQuantity
diff --git a/backend/src/helpers/serviceAccount.ts b/backend/src/helpers/serviceAccount.ts
index f0fe3063e..c8d126491 100644
--- a/backend/src/helpers/serviceAccount.ts
+++ b/backend/src/helpers/serviceAccount.ts
@@ -8,6 +8,7 @@ import {
ServiceTokenData,
IServiceTokenData,
ISecret,
+ IOrganization,
ServiceAccountWorkspacePermission
} from '../models';
import {
@@ -109,9 +110,6 @@ const validateClientForServiceAccount = async ({
environment?: string;
requiredPermissions?: string[];
}) => {
- // TODO: add service account API support for workspace-level endpoints that are not
- // tied to any specific environment
-
if (environment) {
const permission = await ServiceAccountWorkspacePermission.findOne({
serviceAccount,
@@ -123,7 +121,6 @@ const validateClientForServiceAccount = async ({
message: 'Failed service account authorization for the given workspace environment'
});
- // TODO: refactor
let runningIsDisallowed = false;
requiredPermissions?.forEach((requiredPermission: string) => {
switch (requiredPermission) {
@@ -180,7 +177,6 @@ const validateClientForServiceAccount = async ({
});
requiredPermissions?.forEach((requiredPermission: string) => {
- // TODO: refactor
let runningIsDisallowed = false;
requiredPermissions?.forEach((requiredPermission: string) => {
switch (requiredPermission) {
@@ -202,9 +198,6 @@ const validateClientForServiceAccount = async ({
});
});
});
-
- // TODO
- return [];
}
/**
@@ -231,9 +224,30 @@ const validateServiceAccountClientForServiceAccount = ({
}
}
+/**
+ * Validate that service account (client) can access organization [organization]
+ * @param {Object} obj
+ * @param {User} obj.user - service account client
+ * @param {Organization} obj.organization - organization to validate against
+ */
+const validateServiceAccountClientForOrganization = async ({
+ serviceAccount,
+ organization
+}: {
+ serviceAccount: IServiceAccount;
+ organization: IOrganization;
+}) => {
+ if (!serviceAccount.organization.equals(organization._id)) {
+ throw UnauthorizedRequestError({
+ message: 'Failed service account authorization for the given organization'
+ });
+ }
+}
+
export {
validateClientForServiceAccount,
validateServiceAccountClientForWorkspace,
validateServiceAccountClientForSecrets,
- validateServiceAccountClientForServiceAccount
+ validateServiceAccountClientForServiceAccount,
+ validateServiceAccountClientForOrganization
}
\ No newline at end of file
diff --git a/backend/src/helpers/user.ts b/backend/src/helpers/user.ts
index ddf313da9..549991148 100644
--- a/backend/src/helpers/user.ts
+++ b/backend/src/helpers/user.ts
@@ -5,7 +5,9 @@ import {
ISecret,
IServiceAccount,
User,
- Membership
+ Membership,
+ IOrganization,
+ Organization,
} from '../models';
import { sendMail } from './nodemailer';
import { validateMembership } from './membership';
@@ -288,11 +290,39 @@ const validateUserClientForServiceAccount = async ({
}
}
+/**
+ * Validate that user (client) can access organization [organization]
+ * @param {Object} obj
+ * @param {User} obj.user - user client
+ * @param {Organization} obj.organization - organization to validate against
+ */
+ const validateUserClientForOrganization = async ({
+ user,
+ organization,
+ acceptedRoles,
+ acceptedStatuses
+}: {
+ user: IUser;
+ organization: IOrganization;
+ acceptedRoles: Array<'owner' | 'admin' | 'member'>;
+ acceptedStatuses: Array<'invited' | 'accepted'>;
+}) => {
+ const membershipOrg = await validateMembershipOrg({
+ userId: user._id,
+ organizationId: organization._id,
+ acceptedRoles,
+ acceptedStatuses
+ });
+
+ return membershipOrg;
+}
+
export {
setupAccount,
completeAccount,
checkUserDevice,
validateUserClientForWorkspace,
validateUserClientForSecrets,
- validateUserClientForServiceAccount
+ validateUserClientForServiceAccount,
+ validateUserClientForOrganization
};
diff --git a/backend/src/middleware/requireAuth.ts b/backend/src/middleware/requireAuth.ts
index fd828c89b..16191d607 100644
--- a/backend/src/middleware/requireAuth.ts
+++ b/backend/src/middleware/requireAuth.ts
@@ -44,6 +44,7 @@ const requireAuth = ({
acceptedAuthModes: string[];
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
+
// validate auth token against accepted auth modes [acceptedAuthModes]
// and return token type [authTokenType] and value [authTokenValue]
const { authMode, authTokenValue } = validateAuthMode({
@@ -87,7 +88,7 @@ const requireAuth = ({
req.authData = {
authMode,
- authPayload
+ authPayload // User, ServiceAccount, ServiceTokenData
}
return next();
diff --git a/backend/src/middleware/requireMembershipOrgAuth.ts b/backend/src/middleware/requireMembershipOrgAuth.ts
index ea9ed9afc..f3ad32391 100644
--- a/backend/src/middleware/requireMembershipOrgAuth.ts
+++ b/backend/src/middleware/requireMembershipOrgAuth.ts
@@ -20,8 +20,8 @@ const requireMembershipOrgAuth = ({
acceptedStatuses,
location = 'params'
}: {
- acceptedRoles: string[];
- acceptedStatuses: string[];
+ acceptedRoles: Array<'owner' | 'admin' | 'member'>;
+ acceptedStatuses: Array<'invited' | 'accepted'>;
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
diff --git a/backend/src/middleware/requireOrganizationAuth.ts b/backend/src/middleware/requireOrganizationAuth.ts
index 5768b4b36..8d8e967f2 100644
--- a/backend/src/middleware/requireOrganizationAuth.ts
+++ b/backend/src/middleware/requireOrganizationAuth.ts
@@ -3,6 +3,7 @@ import { Types } from 'mongoose';
import { IOrganization, MembershipOrg } from '../models';
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
import { validateMembershipOrg } from '../helpers/membershipOrg';
+import { validateClientForOrganization } from '../helpers/organization';
type req = 'params' | 'body' | 'query';
@@ -16,20 +17,31 @@ type req = 'params' | 'body' | 'query';
const requireOrganizationAuth = ({
acceptedRoles,
acceptedStatuses,
- location = 'params'
+ locationOrganizationId = 'params'
}: {
- acceptedRoles: string[];
- acceptedStatuses: string[];
- location?: req;
+ acceptedRoles: Array<'owner' | 'admin' | 'member'>;
+ acceptedStatuses: Array<'invited' | 'accepted'>;
+ locationOrganizationId?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
- const { organizationId } = req[location];
- req.membershipOrg = await validateMembershipOrg({
- userId: req.user._id,
+ const { organizationId } = req[locationOrganizationId];
+
+ // TODO: incorporate [acceptedRoles] and [acceptedStatuses]
+
+ const { organization, membershipOrg } = await validateClientForOrganization({
+ authData: req.authData,
organizationId: new Types.ObjectId(organizationId),
acceptedRoles,
acceptedStatuses
});
+
+ if (organization) {
+ req.organization = organization;
+ }
+
+ if (membershipOrg) {
+ req.membershipOrg = membershipOrg;
+ }
return next();
};
diff --git a/backend/src/middleware/requireServiceAccountWorkspacePermissionAuth.ts b/backend/src/middleware/requireServiceAccountWorkspacePermissionAuth.ts
index d28c72352..0ceb4f598 100644
--- a/backend/src/middleware/requireServiceAccountWorkspacePermissionAuth.ts
+++ b/backend/src/middleware/requireServiceAccountWorkspacePermissionAuth.ts
@@ -14,8 +14,8 @@ const requireServiceAccountWorkspacePermissionAuth = ({
acceptedStatuses,
location = 'params'
}: {
- acceptedRoles: string[];
- acceptedStatuses: string[];
+ acceptedRoles: Array<'owner' | 'admin' | 'member'>;
+ acceptedStatuses: Array<'invited' | 'accepted'>;
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
diff --git a/backend/src/models/membershipOrg.ts b/backend/src/models/membershipOrg.ts
index b5013acbb..540a4451b 100644
--- a/backend/src/models/membershipOrg.ts
+++ b/backend/src/models/membershipOrg.ts
@@ -1,7 +1,7 @@
-import { Schema, model, Types } from 'mongoose';
+import { Schema, model, Types, Document } from 'mongoose';
import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from '../variables';
-export interface IMembershipOrg {
+export interface IMembershipOrg extends Document {
_id: Types.ObjectId;
user: Types.ObjectId;
inviteEmail: string;
diff --git a/backend/src/routes/v2/serviceAccounts.ts b/backend/src/routes/v2/serviceAccounts.ts
index fef0c87e9..6f0db91b7 100644
--- a/backend/src/routes/v2/serviceAccounts.ts
+++ b/backend/src/routes/v2/serviceAccounts.ts
@@ -53,7 +53,7 @@ router.post(
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
- location: 'body'
+ locationOrganizationId: 'body'
}),
serviceAccountsController.createServiceAccount
);
diff --git a/docs/api-reference/overview/authentication.mdx b/docs/api-reference/overview/authentication.mdx
index 7df8f10e5..3e18f8ce4 100644
--- a/docs/api-reference/overview/authentication.mdx
+++ b/docs/api-reference/overview/authentication.mdx
@@ -1,25 +1,51 @@
---
title: "Authentication"
+description: "How to authenticate with the Infisical Public API"
---
-To authenticate requests with Infisical, you can either use an API Key or [Infisical Token](../../../getting-started/dashboard/token); certain endpoints will accept either one or both.
-- API Key: This general-purpose authentication token provides user access to most endpoints in this reference.
-- [Infisical Token](../../../getting-started/dashboard/token): This authentication token (also referred to as the service token) is scoped to a specific project and environment and used for CRUD secret operations.
+## Essentials
+
+The Public API accepts multiple modes of authentication being via API Key, Service Account credentials, or [Infisical Token](../../../getting-started/dashboard/token).
+
+- API Key: Provides full access to all endpoints representing the user.
+- [Service Account](): Provides scoped access to an organization and select projects representing a machine such as a VM or application client.
+- [Infisical Token](../../../getting-started/dashboard/token): Provides short-lived, scoped CRUD access to the secrets of a specific project and environment.
+The API key mode uses an API key to authenticate with the API.
+
To authenticate requests with Infisical using the API Key, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform.
You can obtain an API key in User Settings > API Keys


+
+
+The Service Account mode uses an Access Key to authenticate with the API and a Public Key and Private Key to perform any cryptographic operations.
+
+To authenticate requests with Infisical using the Access Key, you must include it in the `Authorization` header of HTTP requests made to the platform with the value `Bearer `.
+
+You can create a Service Account in Organization Settings > Service Accounts
+
-To authenticate requests with Infisical using the Infisical Token, you must include your Infisical Token in the `Authorization` header of HTTP requests made to the platform with the value `Bearer st.`.
+
+The Infisical Token mode uses an Infisical Token to authenticate with the API.
+
+To authenticate requests with Infisical using the Infisical Token, you must include your Infisical Token in the `Authorization` header of HTTP requests made to the platform with the value `Bearer `.
You can obtain an Infisical Token in Project Settings > Service Tokens.

-
\ No newline at end of file
+
+
+## Use Cases
+
+Depending on your use case, it may make sense to use one or another authentication mode:
+
+- API Key (not recommended): Use if you need full access to the Public API without needing to access any secrets endpoints (because API keys can't encrypt/decrypt secrets).
+- Service Account (recommeded): Use if you need access to multiple projects and environments in an organization; service accounts can generate short-lived access tokens, making them useful for some complex setups.
+- Service Token (recommeded): Use if you need short-lived, scoped CRUD access to the secrets of a specific project and environment.
\ No newline at end of file
diff --git a/docs/api-reference/overview/introduction.mdx b/docs/api-reference/overview/introduction.mdx
index 3d748314b..585abc8f0 100644
--- a/docs/api-reference/overview/introduction.mdx
+++ b/docs/api-reference/overview/introduction.mdx
@@ -2,11 +2,17 @@
title: "Introduction"
---
-Infisical's REST API provides users an alternative way to programmatically access and manage
+Infisical's Public (REST) API provides users an alternative way to programmatically access and manage
secrets via HTTPS requests. This can be useful for automating tasks, such as
rotating credentials, or for integrating secret management into a larger system.
-With the REST API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more.
+With the Public API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more.
+
+
+ We highly recommend using one of the available SDKs when working with the Infisical API.
+
+ If you decide to make your own requests using the API reference instead, be prepared for a steeper learning curve and more manual work.
+
## Concepts
diff --git a/docs/getting-started/dashboard/organization.mdx b/docs/getting-started/dashboard/organization.mdx
index 63ec2cd49..161a79f14 100644
--- a/docs/getting-started/dashboard/organization.mdx
+++ b/docs/getting-started/dashboard/organization.mdx
@@ -24,6 +24,14 @@ To add a member to your organization, scroll down to the "Organization Members"
projects by default.
+## Service Accounts
+
+Service accounts represent machine identities such as VMs or application clients that can authenticate with Infisical. They can be provisioned read/write permissions for project(s) and environment(s).
+
+To add a service account to your organization, scroll down to the "Service Accounts" section and create a service account. Afterwards, you can press on the edit button beside the service account to provision it permissions.
+
+
+
## Incident contacts
Incident contacts of an organization are alerted if anything abnormal is detected within the operations of an organization.
diff --git a/docs/getting-started/dashboard/project.mdx b/docs/getting-started/dashboard/project.mdx
index ce7df57e3..065835fb3 100644
--- a/docs/getting-started/dashboard/project.mdx
+++ b/docs/getting-started/dashboard/project.mdx
@@ -25,16 +25,16 @@ In most cases, environment variables belong to specific environments: developmen

-### Personal/Shared scoping
+### Personal overrides
-Every environment variable is classified as either personal or shared.
+Every environment variable value can be overriden with a custom value.
-- A personal environment variable is one created by a user of a project to be available for that user only.
-- A shared environment variable is one created by a user of a project to be available for other users of the project.
+- An overriden value can only be read and accesssed by the user that overrode the original shared value.
+- A (default) shared value can be read and accesssed by other users in a project.
-You can toggle the classification of an environment variable by pressing on its settings:
+You can turn overrides on/off by toggling the override/branch icon:
-
+
### Search
@@ -42,12 +42,6 @@ You can search for any environment variable by its key.

-### Sort
-
-You can sort environment variables alphabetically by their keys.
-
-
-
### Hide/Un-hide
You can hide or un-hide the values of your environment variables. By default, the values are hidden for your privacy.
diff --git a/docs/getting-started/quickstart.mdx b/docs/getting-started/quickstart.mdx
index 256ae8f85..280669e70 100644
--- a/docs/getting-started/quickstart.mdx
+++ b/docs/getting-started/quickstart.mdx
@@ -9,7 +9,7 @@ These examples demonstrate how to store and fetch environment variables from [In
1. Login or create an account at `app.infisical.com`.
2. Create a new project.
-3. Populate your environment variables as in the image below.
+3. Keep the default environment variables or populate them as in the image below.

diff --git a/docs/images/dashboard-name-modal-organization.png b/docs/images/dashboard-name-modal-organization.png
index 7fe84675e..59145d21b 100644
Binary files a/docs/images/dashboard-name-modal-organization.png and b/docs/images/dashboard-name-modal-organization.png differ
diff --git a/docs/images/dashboard.png b/docs/images/dashboard.png
index 961b53587..2188debfc 100644
Binary files a/docs/images/dashboard.png and b/docs/images/dashboard.png differ
diff --git a/docs/images/organization-ic.png b/docs/images/organization-ic.png
index d94f4061a..e45ad2d82 100644
Binary files a/docs/images/organization-ic.png and b/docs/images/organization-ic.png differ
diff --git a/docs/images/organization-members.png b/docs/images/organization-members.png
index 321808d51..90344b698 100644
Binary files a/docs/images/organization-members.png and b/docs/images/organization-members.png differ
diff --git a/docs/images/organization-service-accounts.png b/docs/images/organization-service-accounts.png
new file mode 100644
index 000000000..f48e848d3
Binary files /dev/null and b/docs/images/organization-service-accounts.png differ
diff --git a/docs/images/organization.png b/docs/images/organization.png
index 473c31db0..488d6c4cf 100644
Binary files a/docs/images/organization.png and b/docs/images/organization.png differ
diff --git a/docs/images/pit-commits.png b/docs/images/pit-commits.png
index 19cfa4976..22599311a 100644
Binary files a/docs/images/pit-commits.png and b/docs/images/pit-commits.png differ
diff --git a/docs/images/pit-snapshot.png b/docs/images/pit-snapshot.png
index 7e790e875..b7e913108 100644
Binary files a/docs/images/pit-snapshot.png and b/docs/images/pit-snapshot.png differ
diff --git a/docs/images/pit-snapshots.png b/docs/images/pit-snapshots.png
index f22231648..aa1cd52d3 100644
Binary files a/docs/images/pit-snapshots.png and b/docs/images/pit-snapshots.png differ
diff --git a/docs/images/project-download.png b/docs/images/project-download.png
index 8750b6cd3..00b02d9bf 100644
Binary files a/docs/images/project-download.png and b/docs/images/project-download.png differ
diff --git a/docs/images/project-drag-drop.png b/docs/images/project-drag-drop.png
index a283faec6..7ae0ed718 100644
Binary files a/docs/images/project-drag-drop.png and b/docs/images/project-drag-drop.png differ
diff --git a/docs/images/project-envar-override.png b/docs/images/project-envar-override.png
new file mode 100644
index 000000000..d9e01c8fd
Binary files /dev/null and b/docs/images/project-envar-override.png differ
diff --git a/docs/images/project-envar-toggle-open.png b/docs/images/project-envar-toggle-open.png
deleted file mode 100644
index 297299454..000000000
Binary files a/docs/images/project-envar-toggle-open.png and /dev/null differ
diff --git a/docs/images/project-environment.png b/docs/images/project-environment.png
index 5b316511c..e2e0d2959 100644
Binary files a/docs/images/project-environment.png and b/docs/images/project-environment.png differ
diff --git a/docs/images/project-hide.png b/docs/images/project-hide.png
index 69fdb13f0..61aed0bee 100644
Binary files a/docs/images/project-hide.png and b/docs/images/project-hide.png differ
diff --git a/docs/images/project-quickstart.png b/docs/images/project-quickstart.png
index 6d8d7f664..9a9660d1d 100644
Binary files a/docs/images/project-quickstart.png and b/docs/images/project-quickstart.png differ
diff --git a/docs/images/project-search.png b/docs/images/project-search.png
index 7388b34ce..bafc0b2c3 100644
Binary files a/docs/images/project-search.png and b/docs/images/project-search.png differ
diff --git a/docs/images/project-sort.png b/docs/images/project-sort.png
deleted file mode 100644
index 134adc8d5..000000000
Binary files a/docs/images/project-sort.png and /dev/null differ
diff --git a/docs/images/secret-versioning.png b/docs/images/secret-versioning.png
index ec1734289..96dbfde41 100644
Binary files a/docs/images/secret-versioning.png and b/docs/images/secret-versioning.png differ