1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-25 14:05:03 +00:00

Compare commits

..

84 Commits

Author SHA1 Message Date
389bbfcade fix vanta postcss 2024-07-16 21:44:33 -04:00
0b8427a004 Merge pull request from Infisical/feat/added-support-for-oidc-auth-in-cli
feat: added support for oidc auth in cli
2024-07-17 00:51:51 +08:00
8a470772e3 Merge pull request from Infisical/polish-scim-groups
Add SCIM user activation/deactivation
2024-07-16 12:09:50 -04:00
853f3c40bc Adjustments to migration file 2024-07-16 22:20:56 +07:00
fed44f328d Merge pull request from akhilmhdh/feat/aws-kms-sm
fix: slug too big for project fixed
2024-07-16 09:50:08 -04:00
a1d00f2c41 Add SCIM user activation/deactivation 2024-07-16 20:19:27 +07:00
95a68f2c2d Merge pull request from Infisical/improve-auth-method-errors
Improve Native Auth Method Forbidden Errors
2024-07-16 15:00:12 +07:00
db7c0c45f6 Merge pull request from Infisical/fix-identity-projects
Fix Identity-Project Provisioning Modal — Filter Current Org Projects
2024-07-16 14:59:41 +07:00
82bca03162 Filter out only projects that are part of current org in identity project modal 2024-07-16 14:31:40 +07:00
043c04778f Improve native auth method unauthorized errors 2024-07-16 13:47:46 +07:00
=
560cd81a1c fix: slug too big for project fixed 2024-07-16 11:26:45 +05:30
df3a87fabf Merge pull request from Infisical/daniel/operator-azure-fix
feat(k8-operator): customizable azure auth resource url
2024-07-16 06:29:13 +02:00
6eae98c1d4 Update login.mdx 2024-07-16 05:45:48 +02:00
6ceeccf583 Update kubernetes.mdx 2024-07-16 05:25:30 +02:00
9b0b14b847 Merge pull request from Infisical/daniel/azure-fix
fix(auth): Azure audience formatting bug
2024-07-16 04:50:49 +02:00
78f4c0f002 Update Chart.yaml 2024-07-16 04:46:32 +02:00
6cff2f0437 Update values.yaml 2024-07-16 04:46:24 +02:00
6cefb180d6 Update SDK and go mod tidy 2024-07-16 04:44:32 +02:00
59a44155c5 Azure resource 2024-07-16 04:43:53 +02:00
d0ad9c6b17 Update sample.yaml 2024-07-16 04:43:46 +02:00
58a406b114 Update secrets.infisical.com_infisicalsecrets.yaml 2024-07-16 04:43:42 +02:00
8a85695dc5 Custom azure resource 2024-07-16 04:43:38 +02:00
7ed8feee6f Update identity-azure-auth-fns.ts 2024-07-16 04:15:04 +02:00
de67c0ad9f Merge pull request from akhilmhdh/feat/folder-improvement-tf
New folder endpoints for terraform
2024-07-15 21:40:24 -04:00
b8d11d31a6 Merge pull request from Infisical/handbook-update
updated hiring handbook
2024-07-15 21:38:33 -04:00
d630ceaffe updated hiring handbook 2024-07-15 17:19:56 -07:00
a89e60f296 Merge pull request from Infisical/maidul-sddwqdwdwqe123
Remove org read check on project fetch
2024-07-15 16:38:21 -04:00
a5d9abf1c8 remove org read check on projects fetch 2024-07-15 16:33:11 -04:00
d97dea2573 Merge pull request from Infisical/misc/removed-aws-global-config-update
misc: moved aws creds to constructor
2024-07-16 02:38:57 +08:00
bc58f6b988 misc: moved aws creds to constructor 2024-07-16 02:31:31 +08:00
ed8e3f34fb Merge pull request from akhilmhdh/feat/aws-kms-sm
aws kms support base setup
2024-07-15 12:47:30 -04:00
91315c88c3 Merge pull request from Infisical/misc/displayed-project-name-in-slack-webhook
misc: displayed project name in slack webhook
2024-07-16 00:18:42 +08:00
9267f881d6 misc: displayed project name in slack webhook 2024-07-15 16:49:06 +08:00
c2ddb7e2fe misc: updated go-sdk version 2024-07-15 12:26:31 +08:00
c90ecd336c Merge pull request from Infisical/fix-scim-groups
Fix SCIM PATCH /Groups Fn
2024-07-15 00:57:46 +07:00
d8b1da3ddd Fix SCIM patch group fn 2024-07-15 00:29:18 +07:00
58e86382fe Merge pull request from Infisical/update-used-count-on-prem
Update dynamic seat count on prem
2024-07-14 20:13:42 +07:00
2080c4419e Update dynamic seat count on prem 2024-07-14 14:25:32 +07:00
b582a4a06d Merge pull request from DDDASHXD/sebastian/ui-fix
Fix: Features card overflow
2024-07-12 21:05:56 +02:00
a5c6a864de Fix: Features card overflow 2024-07-12 18:57:08 +00:00
5082c1ba3b Merge pull request from Infisical/misc/soft-delete-shared-secrets
misc: soft delete shared secrets upon expiry
2024-07-12 12:56:26 -04:00
cceb08b1b5 Merge pull request from Infisical/misc/address-ws-vulnerability-via-package-update
misc: addressed ws vulnerability via package update
2024-07-12 12:20:15 -04:00
4c34e58945 Merge pull request from Infisical/dependabot/npm_and_yarn/frontend/axios-0.28.0
build(deps): bump axios from 0.27.2 to 0.28.0 in /frontend
2024-07-12 19:17:08 +05:30
72de1901a1 build(deps): bump axios from 0.27.2 to 0.28.0 in /frontend
Bumps [axios](https://github.com/axios/axios) from 0.27.2 to 0.28.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.28.0/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.27.2...v0.28.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 13:37:13 +00:00
65fefcdd87 Merge pull request from Infisical/dependabot/npm_and_yarn/frontend/postcss-8.4.39
build(deps): bump postcss from 8.4.14 to 8.4.39 in /frontend
2024-07-12 19:03:59 +05:30
8e753eda72 misc: soft delete shared secrets 2024-07-12 21:29:17 +08:00
7137c94fa2 build(deps): bump postcss from 8.4.14 to 8.4.39 in /frontend
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.14 to 8.4.39.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.14...8.4.39)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 13:24:54 +00:00
52ea7dfa61 Merge pull request from Infisical/dependabot/go_modules/cli/github.com/rs/cors-1.11.0
build(deps): bump github.com/rs/cors from 1.9.0 to 1.11.0 in /cli
2024-07-12 18:52:25 +05:30
093925ed0e build(deps): bump github.com/rs/cors from 1.9.0 to 1.11.0 in /cli
Bumps [github.com/rs/cors](https://github.com/rs/cors) from 1.9.0 to 1.11.0.
- [Commits](https://github.com/rs/cors/compare/v1.9.0...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/rs/cors
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 13:20:45 +00:00
356afd18c4 feat: added support for oidc auth in cli 2024-07-12 18:28:09 +08:00
4491f2d8f1 Merge pull request from Infisical/dependabot/go_modules/cli/github.com/dvsekhvalnov/jose2go-1.6.0
build(deps): bump github.com/dvsekhvalnov/jose2go from 1.5.0 to 1.6.0 in /cli
2024-07-12 14:44:32 +05:30
4a401957c7 build(deps): bump github.com/dvsekhvalnov/jose2go in /cli
Bumps [github.com/dvsekhvalnov/jose2go](https://github.com/dvsekhvalnov/jose2go) from 1.5.0 to 1.6.0.
- [Commits](https://github.com/dvsekhvalnov/jose2go/compare/v1.5...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/dvsekhvalnov/jose2go
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 09:11:42 +00:00
=
539785acae docs: updated api reference docs for the new folder endpoint 2024-07-12 14:29:19 +05:30
=
3c63346d3a feat: new get-by-id for folder for tf 2024-07-12 14:24:13 +05:30
0c673f6cca misc: addressed ws vulnerability via package update 2024-07-12 15:36:49 +08:00
10f4cbf11f Merge pull request from Infisical/feat/add-share-secret-hide-unhide
feat: add share-secret hide and unhide
2024-07-12 14:57:02 +08:00
a6a8c32326 Merge pull request from Infisical/docs/identity-github-oidc-auth
doc: added docs for connecting github to infisical via oidc auth
2024-07-12 10:26:08 +07:00
99a474dba7 Merge pull request from Infisical/misc/moved-admin-user-deletion-to-pro
misc: moved admin user deletion to pro plan
2024-07-11 13:28:33 -04:00
e439f4e5aa Update UserPanel.tsx 2024-07-11 13:25:48 -04:00
ae2ecf1540 Merge pull request from Infisical/misc/add-ttl-max-value-for-identities
misc: add max checks for TTL values of identities
2024-07-11 13:21:53 -04:00
10214ea5dc misc: renamed button label 2024-07-12 00:45:16 +08:00
918cd414a8 feat: add share-secret hide and unhide 2024-07-12 00:42:26 +08:00
f9a125acee misc: updated limit to 10 years 2024-07-11 23:40:45 +08:00
52415ea83e misc: removed redundant text' 2024-07-11 23:30:55 +08:00
c5ca2b6796 doc: added docs for connecting github to infisical via oidc auth 2024-07-11 23:00:45 +08:00
ef5bcac925 Merge pull request from Infisical/move-groups
Consolidate People and Groups Tabs to shared User Tab at Org / Project Level
2024-07-11 18:58:12 +07:00
6cbeb29b4e Merge remote-tracking branch 'origin/main' into misc/add-ttl-max-value-for-identities 2024-07-11 19:17:25 +08:00
fbe344c0df Fix token auth ref 2024-07-11 14:56:57 +07:00
5821f65a63 Fix token auth ref 2024-07-11 14:56:08 +07:00
3af510d487 Merge pull request from Infisical/fix-token-auth-ref
Fix Token Auth Ref in Access Token DAL
2024-07-11 14:54:35 +07:00
c15adc7df9 Fix token auth ref 2024-07-11 14:49:22 +07:00
93af7573ac Consolidate people and groups tabs to user / user groups shared tab 2024-07-11 13:35:11 +07:00
cddda1148e misc: added max ttl checks for native auths 2024-07-11 14:05:50 +08:00
9c37eeeda6 misc: finalize form validation for universal auth ttl 2024-07-11 13:48:18 +08:00
eadf5bef77 misc: add TTL max values for universal auth 2024-07-11 13:35:58 +08:00
c501c85eb8 misc: renamed to more generic label 2024-07-11 00:14:34 +08:00
=
5d4c7c2cbf feat: added encrypt/decrypt with key for kms service and changed kms encrytion to hoc to avoid back to back db calls 2024-07-10 15:23:02 +05:30
=
08f0bf9c67 feat: fixed migration down missing orgid 2024-07-10 14:47:44 +05:30
=
654dd97793 feat: external kms router defined not plugged in 2024-07-10 12:48:36 +05:30
=
2e7baf8c89 feat: added external kms router but not connected with the server yet 2024-07-10 12:48:35 +05:30
=
7ca7a95070 feat: kms service changes for db change 2024-07-10 12:48:35 +05:30
=
71c49c8b90 feat: kms db schema changes to support external and internal kms uniformly 2024-07-10 12:48:35 +05:30
9832915eba add .? incase adminUserDeletion is empty 2024-07-09 21:09:55 -04:00
b98c8629e5 misc: moved admin user deletion to pro 2024-07-09 23:51:09 +08:00
139 changed files with 4773 additions and 1646 deletions
backend
package-lock.jsonpackage.json
src
@types
db
ee
lib/api-docs
server/routes
services
cli
company
docs
api-reference/endpoints/folders
cli/commands
documentation/platform/identities/oidc-auth
integrations/platforms
mint.json
frontend
helm-charts/secrets-operator
k8-operator

1062
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -106,6 +106,7 @@
},
"dependencies": {
"@aws-sdk/client-iam": "^3.525.0",
"@aws-sdk/client-kms": "^3.609.0",
"@aws-sdk/client-secrets-manager": "^3.504.0",
"@aws-sdk/client-sts": "^3.600.0",
"@casl/ability": "^6.5.0",

@ -9,6 +9,7 @@ import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream
import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service";
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service";
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
@ -163,6 +164,7 @@ declare module "fastify" {
secretSharing: TSecretSharingServiceFactory;
rateLimit: TRateLimitServiceFactory;
userEngagement: TUserEngagementServiceFactory;
externalKms: TExternalKmsServiceFactory;
};
// this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer

@ -59,6 +59,9 @@ import {
TDynamicSecrets,
TDynamicSecretsInsert,
TDynamicSecretsUpdate,
TExternalKms,
TExternalKmsInsert,
TExternalKmsUpdate,
TGitAppInstallSessions,
TGitAppInstallSessionsInsert,
TGitAppInstallSessionsUpdate,
@ -125,6 +128,9 @@ import {
TIntegrations,
TIntegrationsInsert,
TIntegrationsUpdate,
TInternalKms,
TInternalKmsInsert,
TInternalKmsUpdate,
TKmsKeys,
TKmsKeysInsert,
TKmsKeysUpdate,
@ -656,6 +662,8 @@ declare module "knex/types/tables" {
TKmsRootConfigInsert,
TKmsRootConfigUpdate
>;
[TableName.InternalKms]: KnexOriginal.CompositeTableType<TInternalKms, TInternalKmsInsert, TInternalKmsUpdate>;
[TableName.ExternalKms]: KnexOriginal.CompositeTableType<TExternalKms, TExternalKmsInsert, TExternalKmsUpdate>;
[TableName.KmsKey]: KnexOriginal.CompositeTableType<TKmsKeys, TKmsKeysInsert, TKmsKeysUpdate>;
[TableName.KmsKeyVersion]: KnexOriginal.CompositeTableType<
TKmsKeyVersions,

@ -0,0 +1,256 @@
import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TableName } from "../schemas";
const createInternalKmsTableAndBackfillData = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
// building the internal kms table by filling from old kms table
if (doesOldKmsKeyTableExist && !doesInternalKmsTableExist) {
await knex.schema.createTable(TableName.InternalKms, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.binary("encryptedKey").notNullable();
tb.string("encryptionAlgorithm").notNullable();
tb.integer("version").defaultTo(1).notNullable();
tb.uuid("kmsKeyId").unique().notNullable();
tb.foreign("kmsKeyId").references("id").inTable(TableName.KmsKey).onDelete("CASCADE");
});
// copy the old kms and backfill
const oldKmsKey = await knex(TableName.KmsKey).select("version", "encryptedKey", "encryptionAlgorithm", "id");
if (oldKmsKey.length) {
await knex(TableName.InternalKms).insert(
oldKmsKey.map((el) => ({
encryptionAlgorithm: el.encryptionAlgorithm,
encryptedKey: el.encryptedKey,
kmsKeyId: el.id,
version: el.version
}))
);
}
}
};
const renameKmsKeyVersionTableAsInternalKmsKeyVersion = async (knex: Knex) => {
const doesOldKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.KmsKeyVersion);
const doesNewKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.InternalKmsKeyVersion);
if (doesOldKmsKeyVersionTableExist && !doesNewKmsKeyVersionTableExist) {
// because we haven't started using versioning for kms thus no data exist
await knex.schema.renameTable(TableName.KmsKeyVersion, TableName.InternalKmsKeyVersion);
const hasKmsKeyIdColumn = await knex.schema.hasColumn(TableName.InternalKmsKeyVersion, "kmsKeyId");
const hasInternalKmsIdColumn = await knex.schema.hasColumn(TableName.InternalKmsKeyVersion, "internalKmsId");
await knex.schema.alterTable(TableName.InternalKmsKeyVersion, (tb) => {
if (hasKmsKeyIdColumn) tb.dropColumn("kmsKeyId");
if (!hasInternalKmsIdColumn) {
tb.uuid("internalKmsId").notNullable();
tb.foreign("internalKmsId").references("id").inTable(TableName.InternalKms).onDelete("CASCADE");
}
});
}
};
const createExternalKmsKeyTable = async (knex: Knex) => {
const doesExternalKmsServiceExist = await knex.schema.hasTable(TableName.ExternalKms);
if (!doesExternalKmsServiceExist) {
await knex.schema.createTable(TableName.ExternalKms, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.string("provider").notNullable();
tb.binary("encryptedProviderInputs").notNullable();
tb.string("status");
tb.string("statusDetails");
tb.uuid("kmsKeyId").unique().notNullable();
tb.foreign("kmsKeyId").references("id").inTable(TableName.KmsKey).onDelete("CASCADE");
});
}
};
const removeNonRequiredFieldsFromKmsKeyTableAndBackfillRequiredData = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
// building the internal kms table by filling from old kms table
if (doesOldKmsKeyTableExist) {
const hasSlugColumn = await knex.schema.hasColumn(TableName.KmsKey, "slug");
const hasEncryptedKeyColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptedKey");
const hasEncryptionAlgorithmColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptionAlgorithm");
const hasVersionColumn = await knex.schema.hasColumn(TableName.KmsKey, "version");
const hasTimestamps = await knex.schema.hasColumn(TableName.KmsKey, "createdAt");
const hasProjectId = await knex.schema.hasColumn(TableName.KmsKey, "projectId");
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
if (!hasSlugColumn) tb.string("slug", 32);
if (hasEncryptedKeyColumn) tb.dropColumn("encryptedKey");
if (hasEncryptionAlgorithmColumn) tb.dropColumn("encryptionAlgorithm");
if (hasVersionColumn) tb.dropColumn("version");
if (!hasTimestamps) tb.timestamps(true, true, true);
});
// backfill all org id in kms key because its gonna be changed to non nullable
if (hasProjectId && hasOrgId) {
await knex(TableName.KmsKey)
.whereNull("orgId")
.update({
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
orgId: knex(TableName.Project)
.select("orgId")
.where("id", knex.raw("??", [`${TableName.KmsKey}.projectId`]))
});
}
// backfill slugs in kms
const missingSlugs = await knex(TableName.KmsKey).whereNull("slug").select("id");
if (missingSlugs.length) {
await knex(TableName.KmsKey)
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
.insert(missingSlugs.map(({ id }) => ({ id, slug: slugify(alphaNumericNanoId(8).toLowerCase()) })))
.onConflict("id")
.merge();
}
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
if (hasOrgId) tb.uuid("orgId").notNullable().alter();
tb.string("slug", 32).notNullable().alter();
if (hasProjectId) tb.dropColumn("projectId");
if (hasOrgId) tb.unique(["orgId", "slug"]);
});
}
};
/*
* The goal for this migration is split the existing kms key into three table
* the kms-key table would be a container table that contains
* the internal kms key table and external kms table
*/
export async function up(knex: Knex): Promise<void> {
await createInternalKmsTableAndBackfillData(knex);
await renameKmsKeyVersionTableAsInternalKmsKeyVersion(knex);
await removeNonRequiredFieldsFromKmsKeyTableAndBackfillRequiredData(knex);
await createExternalKmsKeyTable(knex);
const doesOrgKmsKeyExist = await knex.schema.hasColumn(TableName.Organization, "kmsDefaultKeyId");
if (!doesOrgKmsKeyExist) {
await knex.schema.alterTable(TableName.Organization, (tb) => {
tb.uuid("kmsDefaultKeyId").nullable();
tb.foreign("kmsDefaultKeyId").references("id").inTable(TableName.KmsKey);
});
}
const doesProjectKmsSecretManagerKeyExist = await knex.schema.hasColumn(TableName.Project, "kmsSecretManagerKeyId");
if (!doesProjectKmsSecretManagerKeyExist) {
await knex.schema.alterTable(TableName.Project, (tb) => {
tb.uuid("kmsSecretManagerKeyId").nullable();
tb.foreign("kmsSecretManagerKeyId").references("id").inTable(TableName.KmsKey);
});
}
}
const renameInternalKmsKeyVersionBackToKmsKeyVersion = async (knex: Knex) => {
const doesInternalKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.InternalKmsKeyVersion);
const doesKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.KmsKeyVersion);
if (doesInternalKmsKeyVersionTableExist && !doesKmsKeyVersionTableExist) {
// because we haven't started using versioning for kms thus no data exist
await knex.schema.renameTable(TableName.InternalKmsKeyVersion, TableName.KmsKeyVersion);
const hasInternalKmsIdColumn = await knex.schema.hasColumn(TableName.KmsKeyVersion, "internalKmsId");
const hasKmsKeyIdColumn = await knex.schema.hasColumn(TableName.KmsKeyVersion, "kmsKeyId");
await knex.schema.alterTable(TableName.KmsKeyVersion, (tb) => {
if (hasInternalKmsIdColumn) tb.dropColumn("internalKmsId");
if (!hasKmsKeyIdColumn) {
tb.uuid("kmsKeyId").notNullable();
tb.foreign("kmsKeyId").references("id").inTable(TableName.KmsKey).onDelete("CASCADE");
}
});
}
};
const bringBackKmsKeyFields = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
if (doesOldKmsKeyTableExist && doesInternalKmsTableExist) {
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
const hasEncryptedKeyColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptedKey");
const hasEncryptionAlgorithmColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptionAlgorithm");
const hasVersionColumn = await knex.schema.hasColumn(TableName.KmsKey, "version");
const hasNullableOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
const hasProjectIdColumn = await knex.schema.hasColumn(TableName.KmsKey, "projectId");
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
if (!hasEncryptedKeyColumn) tb.binary("encryptedKey");
if (!hasEncryptionAlgorithmColumn) tb.string("encryptionAlgorithm");
if (!hasVersionColumn) tb.integer("version").defaultTo(1);
if (hasNullableOrgId) tb.uuid("orgId").nullable().alter();
if (!hasProjectIdColumn) {
tb.string("projectId");
tb.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
}
if (hasSlug) tb.dropColumn("slug");
});
}
};
const backfillKmsKeyFromInternalKmsTable = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
if (doesInternalKmsTableExist && doesOldKmsKeyTableExist) {
// backfill kms key with internal kms data
await knex(TableName.KmsKey).update({
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
encryptedKey: knex(TableName.InternalKms)
.select("encryptedKey")
.where("kmsKeyId", knex.raw("??", [`${TableName.KmsKey}.id`])),
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
encryptionAlgorithm: knex(TableName.InternalKms)
.select("encryptionAlgorithm")
.where("kmsKeyId", knex.raw("??", [`${TableName.KmsKey}.id`])),
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
projectId: knex(TableName.Project)
.select("id")
.where("kmsCertificateKeyId", knex.raw("??", [`${TableName.KmsKey}.id`]))
});
}
};
export async function down(knex: Knex): Promise<void> {
const doesOrgKmsKeyExist = await knex.schema.hasColumn(TableName.Organization, "kmsDefaultKeyId");
if (doesOrgKmsKeyExist) {
await knex.schema.alterTable(TableName.Organization, (tb) => {
tb.dropColumn("kmsDefaultKeyId");
});
}
const doesProjectKmsSecretManagerKeyExist = await knex.schema.hasColumn(TableName.Project, "kmsSecretManagerKeyId");
if (doesProjectKmsSecretManagerKeyExist) {
await knex.schema.alterTable(TableName.Project, (tb) => {
tb.dropColumn("kmsSecretManagerKeyId");
});
}
await renameInternalKmsKeyVersionBackToKmsKeyVersion(knex);
await bringBackKmsKeyFields(knex);
await backfillKmsKeyFromInternalKmsTable(knex);
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
if (doesOldKmsKeyTableExist) {
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
tb.binary("encryptedKey").notNullable().alter();
tb.string("encryptionAlgorithm").notNullable().alter();
});
}
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
if (doesInternalKmsTableExist) await knex.schema.dropTable(TableName.InternalKms);
const doesExternalKmsServiceExist = await knex.schema.hasTable(TableName.ExternalKms);
if (doesExternalKmsServiceExist) await knex.schema.dropTable(TableName.ExternalKms);
}

@ -0,0 +1,25 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.OrgMembership)) {
const doesUserIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "userId");
const doesOrgIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "orgId");
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
t.boolean("isActive").notNullable().defaultTo(true);
if (doesUserIdExist && doesOrgIdExist) t.index(["userId", "orgId"]);
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.OrgMembership)) {
const doesUserIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "userId");
const doesOrgIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "orgId");
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
t.dropColumn("isActive");
if (doesUserIdExist && doesOrgIdExist) t.dropIndex(["userId", "orgId"]);
});
}
}

@ -0,0 +1,23 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const ExternalKmsSchema = z.object({
id: z.string().uuid(),
provider: z.string(),
encryptedProviderInputs: zodBuffer,
status: z.string().nullable().optional(),
statusDetails: z.string().nullable().optional(),
kmsKeyId: z.string().uuid()
});
export type TExternalKms = z.infer<typeof ExternalKmsSchema>;
export type TExternalKmsInsert = Omit<z.input<typeof ExternalKmsSchema>, TImmutableDBKeys>;
export type TExternalKmsUpdate = Partial<Omit<z.input<typeof ExternalKmsSchema>, TImmutableDBKeys>>;

@ -17,6 +17,7 @@ export * from "./certificate-secrets";
export * from "./certificates";
export * from "./dynamic-secret-leases";
export * from "./dynamic-secrets";
export * from "./external-kms";
export * from "./git-app-install-sessions";
export * from "./git-app-org";
export * from "./group-project-membership-roles";
@ -39,6 +40,7 @@ export * from "./identity-universal-auths";
export * from "./incident-contacts";
export * from "./integration-auths";
export * from "./integrations";
export * from "./internal-kms";
export * from "./kms-key-versions";
export * from "./kms-keys";
export * from "./kms-root-config";

@ -0,0 +1,21 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const InternalKmsKeyVersionSchema = z.object({
id: z.string().uuid(),
encryptedKey: zodBuffer,
version: z.number(),
internalKmsId: z.string().uuid()
});
export type TInternalKmsKeyVersion = z.infer<typeof InternalKmsKeyVersionSchema>;
export type TInternalKmsKeyVersionInsert = Omit<z.input<typeof InternalKmsKeyVersionSchema>, TImmutableDBKeys>;
export type TInternalKmsKeyVersionUpdate = Partial<Omit<z.input<typeof InternalKmsKeyVersionSchema>, TImmutableDBKeys>>;

@ -0,0 +1,22 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const InternalKmsSchema = z.object({
id: z.string().uuid(),
encryptedKey: zodBuffer,
encryptionAlgorithm: z.string(),
version: z.number().default(1),
kmsKeyId: z.string().uuid()
});
export type TInternalKms = z.infer<typeof InternalKmsSchema>;
export type TInternalKmsInsert = Omit<z.input<typeof InternalKmsSchema>, TImmutableDBKeys>;
export type TInternalKmsUpdate = Partial<Omit<z.input<typeof InternalKmsSchema>, TImmutableDBKeys>>;

@ -5,20 +5,17 @@
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const KmsKeysSchema = z.object({
id: z.string().uuid(),
encryptedKey: zodBuffer,
encryptionAlgorithm: z.string(),
version: z.number().default(1),
description: z.string().nullable().optional(),
isDisabled: z.boolean().default(false).nullable().optional(),
isReserved: z.boolean().default(true).nullable().optional(),
projectId: z.string().nullable().optional(),
orgId: z.string().uuid().nullable().optional()
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
slug: z.string()
});
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;

@ -96,6 +96,10 @@ export enum TableName {
// KMS Service
KmsServerRootConfig = "kms_root_config",
KmsKey = "kms_keys",
ExternalKms = "external_kms",
InternalKms = "internal_kms",
InternalKmsKeyVersion = "internal_kms_key_version",
// @depreciated
KmsKeyVersion = "kms_key_versions"
}

@ -17,7 +17,8 @@ export const OrgMembershipsSchema = z.object({
userId: z.string().uuid().nullable().optional(),
orgId: z.string().uuid(),
roleId: z.string().uuid().nullable().optional(),
projectFavorites: z.string().array().nullable().optional()
projectFavorites: z.string().array().nullable().optional(),
isActive: z.boolean()
});
export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;

@ -15,7 +15,8 @@ export const OrganizationsSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
authEnforced: z.boolean().default(false).nullable().optional(),
scimEnabled: z.boolean().default(false).nullable().optional()
scimEnabled: z.boolean().default(false).nullable().optional(),
kmsDefaultKeyId: z.string().uuid().nullable().optional()
});
export type TOrganizations = z.infer<typeof OrganizationsSchema>;

@ -19,7 +19,8 @@ export const ProjectsSchema = z.object({
upgradeStatus: z.string().nullable().optional(),
pitVersionLimit: z.number().default(10),
kmsCertificateKeyId: z.string().uuid().nullable().optional(),
auditLogsRetentionDays: z.number().nullable().optional()
auditLogsRetentionDays: z.number().nullable().optional(),
kmsSecretManagerKeyId: z.string().uuid().nullable().optional()
});
export type TProjects = z.infer<typeof ProjectsSchema>;

@ -29,7 +29,8 @@ export async function seed(knex: Knex): Promise<void> {
role: OrgMembershipRole.Admin,
orgId: org.id,
status: OrgMembershipStatus.Accepted,
userId: user.id
userId: user.id,
isActive: true
}
]);
}

@ -0,0 +1,190 @@
import { z } from "zod";
import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
import {
ExternalKmsAwsSchema,
ExternalKmsInputSchema,
ExternalKmsInputUpdateSchema
} from "@app/ee/services/external-kms/providers/model";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
const sanitizedExternalSchema = KmsKeysSchema.extend({
external: ExternalKmsSchema.pick({
id: true,
status: true,
statusDetails: true,
provider: true
})
});
const sanitizedExternalSchemaForGetById = KmsKeysSchema.extend({
external: ExternalKmsSchema.pick({
id: true,
status: true,
statusDetails: true,
provider: true
}).extend({
providerInput: ExternalKmsAwsSchema
})
});
export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
slug: z.string().min(1).trim().toLowerCase().optional(),
description: z.string().min(1).trim().optional(),
provider: ExternalKmsInputSchema
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.create({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.slug,
provider: req.body.provider,
description: req.body.description
});
return { externalKms };
}
});
server.route({
method: "PATCH",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
id: z.string().trim().min(1)
}),
body: z.object({
slug: z.string().min(1).trim().toLowerCase().optional(),
description: z.string().min(1).trim().optional(),
provider: ExternalKmsInputUpdateSchema
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.updateById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.slug,
provider: req.body.provider,
description: req.body.description,
id: req.params.id
});
return { externalKms };
}
});
server.route({
method: "DELETE",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
id: z.string().trim().min(1)
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.deleteById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});
return { externalKms };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
id: z.string().trim().min(1)
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchemaForGetById
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.findById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});
return { externalKms };
}
});
server.route({
method: "GET",
url: "/slug/:slug",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
slug: z.string().trim().min(1)
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchemaForGetById
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.findBySlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.params.slug
});
return { externalKms };
}
});
};

@ -186,7 +186,13 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
})
),
displayName: z.string().trim(),
active: z.boolean()
active: z.boolean(),
groups: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim()
})
)
})
}
},
@ -475,10 +481,13 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
}),
z.object({
op: z.literal("add"),
value: z.object({
value: z.string().trim(),
display: z.string().trim().optional()
})
path: z.string().trim(),
value: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim().optional()
})
)
})
])
)
@ -569,7 +578,13 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
})
),
displayName: z.string().trim(),
active: z.boolean()
active: z.boolean(),
groups: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim()
})
)
})
}
},

@ -17,7 +17,7 @@ type TCertificateAuthorityCrlServiceFactoryDep = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decrypt" | "generateKmsKey">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
@ -68,11 +68,11 @@ export const certificateAuthorityCrlServiceFactory = ({
kmsService
});
const decryptedCrl = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCrl.encryptedCrl
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const decryptedCrl = kmsDecryptor({ cipherTextBlob: caCrl.encryptedCrl });
const crl = new x509.X509Crl(decryptedCrl);
const base64crl = crl.toString("base64");

@ -0,0 +1,47 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TKmsKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TExternalKmsDALFactory = ReturnType<typeof externalKmsDALFactory>;
export const externalKmsDALFactory = (db: TDbClient) => {
const externalKmsOrm = ormify(db, TableName.ExternalKms);
const find = async (filter: Partial<TKmsKeys>, tx?: Knex) => {
try {
const result = await (tx || db.replicaNode())(TableName.ExternalKms)
.join(TableName.KmsKey, `${TableName.KmsKey}.id`, `${TableName.ExternalKms}.kmsKeyId`)
.where(filter)
.select(selectAllTableCols(TableName.KmsKey))
.select(
db.ref("id").withSchema(TableName.ExternalKms).as("externalKmsId"),
db.ref("provider").withSchema(TableName.ExternalKms).as("externalKmsProvider"),
db.ref("encryptedProviderInputs").withSchema(TableName.ExternalKms).as("externalKmsEncryptedProviderInput"),
db.ref("status").withSchema(TableName.ExternalKms).as("externalKmsStatus"),
db.ref("statusDetails").withSchema(TableName.ExternalKms).as("externalKmsStatusDetails")
);
return result.map((el) => ({
id: el.id,
description: el.description,
isDisabled: el.isDisabled,
isReserved: el.isReserved,
orgId: el.orgId,
slug: el.slug,
externalKms: {
id: el.externalKmsId,
provider: el.externalKmsProvider,
status: el.externalKmsStatus,
statusDetails: el.externalKmsStatusDetails
}
}));
} catch (error) {
throw new DatabaseError({ error, name: "Find" });
}
};
return { ...externalKmsOrm, find };
};

@ -0,0 +1,309 @@
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { BadRequestError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TExternalKmsDALFactory } from "./external-kms-dal";
import {
TCreateExternalKmsDTO,
TDeleteExternalKmsDTO,
TGetExternalKmsByIdDTO,
TGetExternalKmsBySlugDTO,
TListExternalKmsDTO,
TUpdateExternalKmsDTO
} from "./external-kms-types";
import { AwsKmsProviderFactory } from "./providers/aws-kms";
import { ExternalKmsAwsSchema, KmsProviders } from "./providers/model";
type TExternalKmsServiceFactoryDep = {
externalKmsDAL: TExternalKmsDALFactory;
kmsService: Pick<TKmsServiceFactory, "getOrgKmsKeyId" | "encryptWithKmsKey" | "decryptWithKmsKey">;
kmsDAL: Pick<TKmsKeyDALFactory, "create" | "updateById" | "findById" | "deleteById" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
};
export type TExternalKmsServiceFactory = ReturnType<typeof externalKmsServiceFactory>;
export const externalKmsServiceFactory = ({
externalKmsDAL,
permissionService,
kmsService,
kmsDAL
}: TExternalKmsServiceFactoryDep) => {
const create = async ({
provider,
description,
actor,
slug,
actorId,
actorOrgId,
actorAuthMethod
}: TCreateExternalKmsDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const kmsSlug = slug ? slugify(slug) : slugify(alphaNumericNanoId(8).toLowerCase());
let sanitizedProviderInput = "";
switch (provider.type) {
case KmsProviders.Aws:
{
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs });
await externalKms.validateConnection();
// if missing kms key this generate a new kms key id and returns new provider input
const newProviderInput = await externalKms.generateInputKmsKey();
sanitizedProviderInput = JSON.stringify(newProviderInput);
}
break;
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId);
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: orgKmsKeyId
});
const { cipherTextBlob: encryptedProviderInputs } = kmsEncryptor({
plainText: Buffer.from(sanitizedProviderInput, "utf8")
});
const externalKms = await externalKmsDAL.transaction(async (tx) => {
const kms = await kmsDAL.create(
{
isReserved: false,
description,
slug: kmsSlug,
orgId: actorOrgId
},
tx
);
const externalKmsCfg = await externalKmsDAL.create(
{
provider: provider.type,
encryptedProviderInputs,
kmsKeyId: kms.id
},
tx
);
return { ...kms, external: externalKmsCfg };
});
return externalKms;
};
const updateById = async ({
provider,
description,
actor,
id: kmsId,
slug,
actorId,
actorOrgId,
actorAuthMethod
}: TUpdateExternalKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const kmsSlug = slug ? slugify(slug) : undefined;
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
let sanitizedProviderInput = "";
if (provider) {
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
});
switch (provider.type) {
case KmsProviders.Aws:
{
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
);
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
await externalKms.validateConnection();
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
}
break;
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
}
let encryptedProviderInputs: Buffer | undefined;
if (sanitizedProviderInput) {
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const { cipherTextBlob } = kmsEncryptor({
plainText: Buffer.from(sanitizedProviderInput, "utf8")
});
encryptedProviderInputs = cipherTextBlob;
}
const externalKms = await externalKmsDAL.transaction(async (tx) => {
const kms = await kmsDAL.updateById(
kmsDoc.id,
{
description,
slug: kmsSlug
},
tx
);
if (encryptedProviderInputs) {
const externalKmsCfg = await externalKmsDAL.updateById(
externalKmsDoc.id,
{
encryptedProviderInputs
},
tx
);
return { ...kms, external: externalKmsCfg };
}
return { ...kms, external: externalKmsDoc };
});
return externalKms;
};
const deleteById = async ({ actor, id: kmsId, actorId, actorOrgId, actorAuthMethod }: TDeleteExternalKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const externalKms = await externalKmsDAL.transaction(async (tx) => {
const kms = await kmsDAL.deleteById(kmsDoc.id, tx);
return { ...kms, external: externalKmsDoc };
});
return externalKms;
};
const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListExternalKmsDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDocs = await externalKmsDAL.find({ orgId: actorOrgId });
return externalKmsDocs;
};
const findById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id: kmsId }: TGetExternalKmsByIdDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
});
switch (externalKmsDoc.provider) {
case KmsProviders.Aws: {
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
);
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
}
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
};
const findBySlug = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
slug: kmsSlug
}: TGetExternalKmsBySlugDTO) => {
const kmsDoc = await kmsDAL.findOne({ slug: kmsSlug, orgId: actorOrgId });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
});
switch (externalKmsDoc.provider) {
case KmsProviders.Aws: {
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
);
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
}
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
};
return {
create,
updateById,
deleteById,
list,
findById,
findBySlug
};
};

@ -0,0 +1,30 @@
import { TOrgPermission } from "@app/lib/types";
import { TExternalKmsInputSchema, TExternalKmsInputUpdateSchema } from "./providers/model";
export type TCreateExternalKmsDTO = {
slug?: string;
description?: string;
provider: TExternalKmsInputSchema;
} & Omit<TOrgPermission, "orgId">;
export type TUpdateExternalKmsDTO = {
id: string;
slug?: string;
description?: string;
provider?: TExternalKmsInputUpdateSchema;
} & Omit<TOrgPermission, "orgId">;
export type TDeleteExternalKmsDTO = {
id: string;
} & Omit<TOrgPermission, "orgId">;
export type TListExternalKmsDTO = Omit<TOrgPermission, "orgId">;
export type TGetExternalKmsByIdDTO = {
id: string;
} & Omit<TOrgPermission, "orgId">;
export type TGetExternalKmsBySlugDTO = {
slug: string;
} & Omit<TOrgPermission, "orgId">;

@ -0,0 +1,102 @@
import { CreateKeyCommand, DecryptCommand, DescribeKeyCommand, EncryptCommand, KMSClient } from "@aws-sdk/client-kms";
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
import { randomUUID } from "crypto";
import { ExternalKmsAwsSchema, KmsAwsCredentialType, TExternalKmsAwsSchema, TExternalKmsProviderFns } from "./model";
const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => {
if (providerInputs.credential.type === KmsAwsCredentialType.AssumeRole) {
const awsCredential = providerInputs.credential.data;
const stsClient = new STSClient({
region: providerInputs.awsRegion
});
const command = new AssumeRoleCommand({
RoleArn: awsCredential.assumeRoleArn,
RoleSessionName: `infisical-kms-${randomUUID()}`,
DurationSeconds: 900, // 15mins
ExternalId: awsCredential.externalId
});
const response = await stsClient.send(command);
if (!response.Credentials?.AccessKeyId || !response.Credentials?.SecretAccessKey)
throw new Error("Failed to assume role");
const kmsClient = new KMSClient({
region: providerInputs.awsRegion,
credentials: {
accessKeyId: response.Credentials.AccessKeyId,
secretAccessKey: response.Credentials.SecretAccessKey,
sessionToken: response.Credentials.SessionToken,
expiration: response.Credentials.Expiration
}
});
return kmsClient;
}
const awsCredential = providerInputs.credential.data;
const kmsClient = new KMSClient({
region: providerInputs.awsRegion,
credentials: {
accessKeyId: awsCredential.accessKey,
secretAccessKey: awsCredential.secretKey
}
});
return kmsClient;
};
type AwsKmsProviderArgs = {
inputs: unknown;
};
type TAwsKmsProviderFactoryReturn = TExternalKmsProviderFns & {
generateInputKmsKey: () => Promise<TExternalKmsAwsSchema>;
};
export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Promise<TAwsKmsProviderFactoryReturn> => {
const providerInputs = await ExternalKmsAwsSchema.parseAsync(inputs);
const awsClient = await getAwsKmsClient(providerInputs);
const generateInputKmsKey = async () => {
if (providerInputs.kmsKeyId) return providerInputs;
const command = new CreateKeyCommand({ Tags: [{ TagKey: "author", TagValue: "infisical" }] });
const kmsKey = await awsClient.send(command);
if (!kmsKey.KeyMetadata?.KeyId) throw new Error("Failed to generate kms key");
return { ...providerInputs, kmsKeyId: kmsKey.KeyMetadata?.KeyId };
};
const validateConnection = async () => {
const command = new DescribeKeyCommand({
KeyId: providerInputs.kmsKeyId
});
const isConnected = await awsClient.send(command).then(() => true);
return isConnected;
};
const encrypt = async (data: Buffer) => {
const command = new EncryptCommand({
KeyId: providerInputs.kmsKeyId,
Plaintext: data
});
const encryptionCommand = await awsClient.send(command);
if (!encryptionCommand.CiphertextBlob) throw new Error("encryption failed");
return { encryptedBlob: Buffer.from(encryptionCommand.CiphertextBlob) };
};
const decrypt = async (encryptedBlob: Buffer) => {
const command = new DecryptCommand({
KeyId: providerInputs.kmsKeyId,
CiphertextBlob: encryptedBlob
});
const decryptionCommand = await awsClient.send(command);
if (!decryptionCommand.Plaintext) throw new Error("decryption failed");
return { data: Buffer.from(decryptionCommand.Plaintext) };
};
return {
generateInputKmsKey,
validateConnection,
encrypt,
decrypt
};
};

@ -0,0 +1,61 @@
import { z } from "zod";
export enum KmsProviders {
Aws = "aws"
}
export enum KmsAwsCredentialType {
AssumeRole = "assume-role",
AccessKey = "access-key"
}
export const ExternalKmsAwsSchema = z.object({
credential: z
.discriminatedUnion("type", [
z.object({
type: z.literal(KmsAwsCredentialType.AccessKey),
data: z.object({
accessKey: z.string().trim().min(1).describe("AWS user account access key"),
secretKey: z.string().trim().min(1).describe("AWS user account secret key")
})
}),
z.object({
type: z.literal(KmsAwsCredentialType.AssumeRole),
data: z.object({
assumeRoleArn: z.string().trim().min(1).describe("AWS user role to be assumed by infisical"),
externalId: z
.string()
.trim()
.min(1)
.optional()
.describe("AWS assume role external id for furthur security in authentication")
})
})
])
.describe("AWS credential information to connect"),
awsRegion: z.string().min(1).trim().describe("AWS region to connect"),
kmsKeyId: z
.string()
.trim()
.optional()
.describe("A pre existing AWS KMS key id to be used for encryption. If not provided a kms key will be generated.")
});
export type TExternalKmsAwsSchema = z.infer<typeof ExternalKmsAwsSchema>;
// The root schema of the JSON
export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema })
]);
export type TExternalKmsInputSchema = z.infer<typeof ExternalKmsInputSchema>;
export const ExternalKmsInputUpdateSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema.partial() })
]);
export type TExternalKmsInputUpdateSchema = z.infer<typeof ExternalKmsInputUpdateSchema>;
// generic function shared by all provider
export type TExternalKmsProviderFns = {
validateConnection: () => Promise<boolean>;
encrypt: (data: Buffer) => Promise<{ encryptedBlob: Buffer }>;
decrypt: (encryptedBlob: Buffer) => Promise<{ data: Buffer }>;
};

@ -162,11 +162,26 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
}
};
const findUserGroupMembershipsInOrg = async (userId: string, orgId: string) => {
try {
const docs = await db
.replicaNode()(TableName.UserGroupMembership)
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
.where(`${TableName.UserGroupMembership}.userId`, userId)
.where(`${TableName.Groups}.orgId`, orgId);
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "findTest" });
}
};
return {
...userGroupMembershipOrm,
filterProjectsByUserMembership,
findUserGroupMembershipsInProject,
findGroupMembersNotInProject,
deletePendingUserGroupMembershipsByUserIds
deletePendingUserGroupMembershipsByUserIds,
findUserGroupMembershipsInOrg
};
};

@ -449,7 +449,8 @@ export const ldapConfigServiceFactory = ({
userId: userAlias.userId,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Accepted
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
@ -534,7 +535,8 @@ export const ldapConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);

@ -38,7 +38,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
has_used_trial: true,
secretApproval: false,
secretRotation: true,
caCrl: false
caCrl: false,
instanceUserManagement: false
});
export const setupLicenceRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {

@ -218,6 +218,8 @@ export const licenseServiceFactory = ({
} else if (instanceType === InstanceType.EnterpriseOnPrem) {
const usedSeats = await licenseDAL.countOfOrgMembers(null, tx);
const usedIdentitySeats = await licenseDAL.countOrgUsersAndIdentities(null, tx);
onPremFeatures.membersUsed = usedSeats;
onPremFeatures.identitiesUsed = usedIdentitySeats;
await licenseServerOnPremApi.request.patch(`/api/license/v1/license`, {
usedSeats,
usedIdentitySeats

@ -30,9 +30,9 @@ export type TFeatureSet = {
workspacesUsed: 0;
dynamicSecret: false;
memberLimit: null;
membersUsed: 0;
membersUsed: number;
identityLimit: null;
identitiesUsed: 0;
identitiesUsed: number;
environmentLimit: null;
environmentsUsed: 0;
secretVersioning: true;
@ -56,6 +56,7 @@ export type TFeatureSet = {
secretApproval: false;
secretRotation: true;
caCrl: false;
instanceUserManagement: false;
};
export type TOrgPlansTableDTO = {

@ -193,7 +193,8 @@ export const oidcConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@ -266,7 +267,8 @@ export const oidcConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);

@ -109,6 +109,9 @@ export const permissionServiceFactory = ({
authMethod: ActorAuthMethod,
userOrgId?: string
) => {
// when token is scoped, ensure the passed org id is same as user org id
if (userOrgId && userOrgId !== orgId)
throw new BadRequestError({ message: "Invalid user token. Scoped to different organization." });
const membership = await permissionDAL.getOrgPermission(userId, orgId);
if (!membership) throw new UnauthorizedError({ name: "User not in org" });
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {

@ -370,7 +370,8 @@ export const samlConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@ -457,7 +458,8 @@ export const samlConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);

@ -32,12 +32,19 @@ export const parseScimFilter = (filterToParse: string | undefined) => {
return { [attributeName]: parsedValue.replace(/"/g, "") };
};
export function extractScimValueFromPath(path: string): string | null {
const regex = /members\[value eq "([^"]+)"\]/;
const match = path.match(regex);
return match ? match[1] : null;
}
export const buildScimUser = ({
orgMembershipId,
username,
email,
firstName,
lastName,
groups = [],
active
}: {
orgMembershipId: string;
@ -45,6 +52,10 @@ export const buildScimUser = ({
email?: string | null;
firstName: string;
lastName: string;
groups?: {
value: string;
display: string;
}[];
active: boolean;
}): TScimUser => {
const scimUser = {
@ -67,7 +78,7 @@ export const buildScimUser = ({
]
: [],
active,
groups: [],
groups,
meta: {
resourceType: "User",
location: null

@ -2,7 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import jwt from "jsonwebtoken";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TOrgMemberships, TUsers } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@ -30,7 +30,14 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
import {
buildScimGroup,
buildScimGroupList,
buildScimUser,
buildScimUserList,
extractScimValueFromPath,
parseScimFilter
} from "./scim-fns";
import {
TCreateScimGroupDTO,
TCreateScimTokenDTO,
@ -61,17 +68,22 @@ type TScimServiceFactoryDep = {
TOrgDALFactory,
"createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction" | "updateMembershipById"
>;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById" | "findById">;
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
groupDAL: Pick<
TGroupDALFactory,
"create" | "findOne" | "findAllGroupMembers" | "update" | "delete" | "findGroups" | "transaction"
"create" | "findOne" | "findAllGroupMembers" | "delete" | "findGroups" | "transaction" | "updateById" | "update"
>;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,
"find" | "transaction" | "insertMany" | "filterProjectsByUserMembership" | "delete"
| "find"
| "transaction"
| "insertMany"
| "filterProjectsByUserMembership"
| "delete"
| "findUserGroupMembershipsInOrg"
>;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
@ -197,14 +209,14 @@ export const scimServiceFactory = ({
findOpts
);
const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email }) =>
const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email, isActive }) =>
buildScimUser({
orgMembershipId: id ?? "",
username: externalId ?? username,
firstName: firstName ?? "",
lastName: lastName ?? "",
email,
active: true
active: isActive
})
);
@ -240,13 +252,19 @@ export const scimServiceFactory = ({
status: 403
});
const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId);
return buildScimUser({
orgMembershipId: membership.id,
username: membership.externalId ?? membership.username,
email: membership.email ?? "",
firstName: membership.firstName as string,
lastName: membership.lastName as string,
active: true
active: membership.isActive,
groups: groupMembershipsInOrg.map((group) => ({
value: group.groupId,
display: group.name
}))
});
};
@ -296,7 +314,8 @@ export const scimServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@ -364,7 +383,8 @@ export const scimServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@ -401,7 +421,7 @@ export const scimServiceFactory = ({
firstName: createdUser.firstName as string,
lastName: createdUser.lastName as string,
email: createdUser.email ?? "",
active: true
active: createdOrgMembership.isActive
});
};
@ -445,14 +465,8 @@ export const scimServiceFactory = ({
});
if (!active) {
await deleteOrgMembershipFn({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
await orgMembershipDAL.updateById(membership.id, {
isActive: false
});
}
@ -491,17 +505,11 @@ export const scimServiceFactory = ({
status: 403
});
if (!active) {
await deleteOrgMembershipFn({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
});
}
await orgMembershipDAL.updateById(membership.id, {
isActive: active
});
const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId);
return buildScimUser({
orgMembershipId: membership.id,
@ -509,7 +517,11 @@ export const scimServiceFactory = ({
email: membership.email,
firstName: membership.firstName as string,
lastName: membership.lastName as string,
active
active,
groups: groupMembershipsInOrg.map((group) => ({
value: group.groupId,
display: group.name
}))
});
};
@ -817,7 +829,6 @@ export const scimServiceFactory = ({
});
};
// TODO: add support for add/remove op
const updateScimGroupNamePatch = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.groups)
@ -840,27 +851,60 @@ export const scimServiceFactory = ({
status: 403
});
let group: TGroups | undefined;
let group = await groupDAL.findOne({
id: groupId,
orgId
});
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
for await (const operation of operations) {
switch (operation.op) {
case "replace": {
await groupDAL.update(
{
id: groupId,
orgId
},
{
name: operation.value.displayName
}
);
group = await groupDAL.updateById(group.id, {
name: operation.value.displayName
});
break;
}
case "add": {
// TODO
const orgMemberships = await orgMembershipDAL.find({
$in: {
id: operation.value.map((member) => member.value)
}
});
await addUsersToGroupByUserIds({
group,
userIds: orgMemberships.map((membership) => membership.userId as string),
userDAL,
userGroupMembershipDAL,
orgDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL
});
break;
}
case "remove": {
// TODO
const orgMembershipId = extractScimValueFromPath(operation.path);
if (!orgMembershipId) throw new ScimRequestError({ detail: "Invalid path value", status: 400 });
const orgMembership = await orgMembershipDAL.findById(orgMembershipId);
if (!orgMembership) throw new ScimRequestError({ detail: "Org Membership Not Found", status: 400 });
await removeUsersFromGroupByUserIds({
group,
userIds: [orgMembership.userId as string],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL
});
break;
}
default: {
@ -872,13 +916,6 @@ export const scimServiceFactory = ({
}
}
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
return buildScimGroup({
groupId: group.id,
name: group.name,

@ -125,10 +125,11 @@ type TRemoveOp = {
type TAddOp = {
op: "add";
path: string;
value: {
value: string;
display?: string;
};
}[];
};
export type TDeleteScimGroupDTO = {
@ -157,7 +158,10 @@ export type TScimUser = {
type: string;
}[];
active: boolean;
groups: string[];
groups: {
value: string;
display: string;
}[];
meta: {
resourceType: string;
location: null;

@ -515,6 +515,9 @@ export const FOLDERS = {
path: "The path to list folders from.",
directory: "The directory to list folders from. (Deprecated in favor of path)"
},
GET_BY_ID: {
folderId: "The id of the folder to get details."
},
CREATE: {
workspaceId: "The ID of the project to create the folder in.",
environment: "The slug of the environment to create the folder in.",

@ -22,6 +22,8 @@ import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/pro
import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal";
import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue";
import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
import { externalKmsDALFactory } from "@app/ee/services/external-kms/external-kms-dal";
import { externalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
import { groupDALFactory } from "@app/ee/services/group/group-dal";
import { groupServiceFactory } from "@app/ee/services/group/group-service";
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@ -116,7 +118,8 @@ import { integrationDALFactory } from "@app/services/integration/integration-dal
import { integrationServiceFactory } from "@app/services/integration/integration-service";
import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal";
import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
import { kmsDALFactory } from "@app/services/kms/kms-dal";
import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal";
import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { kmsServiceFactory } from "@app/services/kms/kms-service";
import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal";
@ -288,7 +291,9 @@ export const registerRoutes = async (
const dynamicSecretDAL = dynamicSecretDALFactory(db);
const dynamicSecretLeaseDAL = dynamicSecretLeaseDALFactory(db);
const kmsDAL = kmsDALFactory(db);
const kmsDAL = kmskeyDALFactory(db);
const internalKmsDAL = internalKmsDALFactory(db);
const externalKmsDAL = externalKmsDALFactory(db);
const kmsRootConfigDAL = kmsRootConfigDALFactory(db);
const permissionService = permissionServiceFactory({
@ -302,7 +307,16 @@ export const registerRoutes = async (
const kmsService = kmsServiceFactory({
kmsRootConfigDAL,
keyStore,
kmsDAL
kmsDAL,
internalKmsDAL,
orgDAL,
projectDAL
});
const externalKmsService = externalKmsServiceFactory({
kmsDAL,
kmsService,
permissionService,
externalKmsDAL
});
const trustedIpService = trustedIpServiceFactory({
@ -331,7 +345,7 @@ export const registerRoutes = async (
permissionService,
secretApprovalPolicyDAL
});
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL });
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
const samlService = samlConfigServiceFactory({
permissionService,
@ -469,7 +483,8 @@ export const registerRoutes = async (
authService: loginService,
serverCfgDAL: superAdminDAL,
orgService,
keyStore
keyStore,
licenseService
});
const rateLimitService = rateLimitServiceFactory({
rateLimitDAL,
@ -644,7 +659,8 @@ export const registerRoutes = async (
const webhookService = webhookServiceFactory({
permissionService,
webhookDAL,
projectEnvDAL
projectEnvDAL,
projectDAL
});
const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService });
@ -1029,7 +1045,8 @@ export const registerRoutes = async (
projectUserAdditionalPrivilege: projectUserAdditionalPrivilegeService,
identityProjectAdditionalPrivilege: identityProjectAdditionalPrivilegeService,
secretSharing: secretSharingService,
userEngagement: userEngagementService
userEngagement: userEngagementService,
externalKms: externalKmsService
});
const cronJobs: CronJob[] = [];

@ -100,6 +100,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -108,6 +109,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -182,11 +184,12 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
.min(1)
.optional()
.describe(AWS_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).optional().describe(AWS_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(AWS_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AWS_AUTH.UPDATE.accessTokenNumUsesLimit),
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -90,6 +90,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -98,6 +99,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -173,11 +175,12 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
.min(1)
.optional()
.describe(AZURE_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).optional().describe(AZURE_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(AZURE_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AZURE_AUTH.UPDATE.accessTokenNumUsesLimit),
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -91,6 +91,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -99,6 +100,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -175,11 +177,12 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
.min(1)
.optional()
.describe(GCP_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).optional().describe(GCP_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(GCP_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(GCP_AUTH.UPDATE.accessTokenNumUsesLimit),
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -106,6 +106,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -114,6 +115,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -196,7 +198,13 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
.min(1)
.optional()
.describe(KUBERNETES_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).optional().describe(KUBERNETES_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z
.number()
.int()
.min(0)
.max(315360000)
.optional()
.describe(KUBERNETES_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z
.number()
.int()
@ -206,6 +214,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -106,6 +106,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -114,6 +115,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -201,6 +203,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -209,6 +212,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -51,8 +51,6 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
orgId: req.body.organizationId
});
console.log("test");
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.body.organizationId,

@ -39,6 +39,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -47,6 +48,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -117,11 +119,12 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
.min(1)
.optional()
.describe(TOKEN_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).optional().describe(TOKEN_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(TOKEN_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(TOKEN_AUTH.UPDATE.accessTokenNumUsesLimit),
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -107,6 +107,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -115,6 +116,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -196,7 +198,13 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.min(1)
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).optional().describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z
.number()
.int()
.min(0)
.max(315360000)
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z
.number()
.int()
@ -206,6 +214,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -362,7 +371,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
body: z.object({
description: z.string().trim().default("").describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.description),
numUsesLimit: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.numUsesLimit),
ttl: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.ttl)
ttl: z.number().min(0).max(315360000).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.ttl)
}),
response: {
200: z.object({

@ -292,4 +292,39 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
return { folders };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
description: "Get folder by id",
security: [
{
bearerAuth: []
}
],
params: z.object({
id: z.string().trim().describe(FOLDERS.GET_BY_ID.folderId)
}),
response: {
200: z.object({
folder: SecretFoldersSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const folder = await server.services.folder.getFolderById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});
return { folder };
}
});
};

@ -4,7 +4,8 @@ import bcrypt from "bcrypt";
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors";
import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { AuthModeJwtTokenPayload } from "../auth/auth-type";
import { TUserDALFactory } from "../user/user-dal";
@ -14,6 +15,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo
type TAuthTokenServiceFactoryDep = {
tokenDAL: TTokenDALFactory;
userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOne">;
};
export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>;
@ -67,7 +69,7 @@ export const getTokenConfig = (tokenType: TokenType) => {
}
};
export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => {
export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAuthTokenServiceFactoryDep) => {
const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => {
const { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig();
@ -154,6 +156,16 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact
const user = await userDAL.findById(session.userId);
if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" });
if (token.organizationId) {
const orgMembership = await orgMembershipDAL.findOne({
userId: user.id,
orgId: token.organizationId
});
if (!orgMembership) throw new ForbiddenRequestError({ message: "User not member of organization" });
if (!orgMembership.isActive) throw new ForbiddenRequestError({ message: "User not active in organization" });
}
return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId };
};

@ -75,8 +75,10 @@ export const getCaCredentials = async ({
kmsService
});
const decryptedPrivateKey = await kmsService.decrypt({
kmsId: keyId,
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const decryptedPrivateKey = kmsDecryptor({
cipherTextBlob: caSecret.encryptedPrivateKey
});
@ -123,15 +125,17 @@ export const getCaCertChain = async ({
kmsService
});
const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const decryptedCaCert = kmsDecryptor({
cipherTextBlob: caCert.encryptedCertificate
});
const caCertObj = new x509.X509Certificate(decryptedCaCert);
const decryptedChain = await kmsService.decrypt({
kmsId: keyId,
const decryptedChain = kmsDecryptor({
cipherTextBlob: caCert.encryptedCertificateChain
});
@ -168,8 +172,11 @@ export const rebuildCaCrl = async ({
kmsService
});
const privateKey = await kmsService.decrypt({
kmsId: keyId,
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const privateKey = kmsDecryptor({
cipherTextBlob: caSecret.encryptedPrivateKey
});
@ -200,8 +207,10 @@ export const rebuildCaCrl = async ({
signingKey: sk
});
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: keyId
});
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(crl.rawData))
});

@ -25,7 +25,7 @@ type TCertificateAuthorityQueueFactoryDep = {
certificateAuthoritySecretDAL: TCertificateAuthoritySecretDALFactory;
certificateDAL: TCertificateDALFactory;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
queueService: TQueueServiceFactory;
};
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
@ -88,8 +88,10 @@ export const certificateAuthorityQueueFactory = ({
kmsService
});
const privateKey = await kmsService.decrypt({
kmsId: keyId,
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const privateKey = kmsDecryptor({
cipherTextBlob: caSecret.encryptedPrivateKey
});
@ -120,8 +122,10 @@ export const certificateAuthorityQueueFactory = ({
signingKey: sk
});
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: keyId
});
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(crl.rawData))
});

@ -53,7 +53,7 @@ type TCertificateAuthorityServiceFactoryDep = {
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
@ -154,11 +154,14 @@ export const certificateAuthorityServiceFactory = ({
tx
);
const keyId = await getProjectKmsCertificateKeyId({
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
projectId: project.id,
projectDAL,
kmsService
});
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: certificateManagerKmsId
});
if (type === CaType.ROOT) {
// note: create self-signed cert only applicable for root CA
@ -178,13 +181,11 @@ export const certificateAuthorityServiceFactory = ({
]
});
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(cert.rawData))
});
const { cipherTextBlob: encryptedCertificateChain } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedCertificateChain } = kmsEncryptor({
plainText: Buffer.alloc(0)
});
@ -208,8 +209,7 @@ export const certificateAuthorityServiceFactory = ({
signingKey: keys.privateKey
});
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(crl.rawData))
});
@ -224,8 +224,7 @@ export const certificateAuthorityServiceFactory = ({
// https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
const skObj = KeyObject.from(keys.privateKey);
const { cipherTextBlob: encryptedPrivateKey } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedPrivateKey } = kmsEncryptor({
plainText: skObj.export({
type: "pkcs8",
format: "der"
@ -449,15 +448,17 @@ export const certificateAuthorityServiceFactory = ({
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
const keyId = await getProjectKmsCertificateKeyId({
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
const decryptedCaCert = kmsDecryptor({
cipherTextBlob: caCert.encryptedCertificate
});
@ -605,19 +606,20 @@ export const certificateAuthorityServiceFactory = ({
dn: parentCertSubject
});
const keyId = await getProjectKmsCertificateKeyId({
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(certObj.rawData))
});
const { cipherTextBlob: encryptedCertificateChain } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedCertificateChain } = kmsEncryptor({
plainText: Buffer.from(certificateChain)
});
@ -682,14 +684,16 @@ export const certificateAuthorityServiceFactory = ({
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" });
const keyId = await getProjectKmsCertificateKeyId({
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
const decryptedCaCert = kmsDecryptor({
cipherTextBlob: caCert.encryptedCertificate
});
@ -796,8 +800,10 @@ export const certificateAuthorityServiceFactory = ({
const skLeafObj = KeyObject.from(leafKeys.privateKey);
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
});

@ -95,7 +95,7 @@ export type TGetCaCredentialsDTO = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decrypt" | "generateKmsKey">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
};
export type TGetCaCertChainDTO = {
@ -103,7 +103,7 @@ export type TGetCaCertChainDTO = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decrypt" | "generateKmsKey">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
};
export type TRebuildCaCrlDTO = {
@ -113,7 +113,7 @@ export type TRebuildCaCrlDTO = {
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
certificateDAL: Pick<TCertificateDALFactory, "find">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decrypt" | "encrypt">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey" | "encryptWithKmsKey">;
};
export type TRotateCaCrlTriggerDTO = {

@ -25,7 +25,7 @@ type TCertificateServiceFactoryDep = {
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "update">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
@ -164,14 +164,16 @@ export const certificateServiceFactory = ({
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
const keyId = await getProjectKmsCertificateKeyId({
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const decryptedCert = await kmsService.decrypt({
kmsId: keyId,
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: certificateManagerKeyId
});
const decryptedCert = kmsDecryptor({
cipherTextBlob: certBody.encryptedCertificate
});

@ -57,7 +57,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
`${TableName.IdentityOidcAuth}.identityId`
);
})
.leftJoin(TableName.IdentityOidcAuth, (qb) => {
.leftJoin(TableName.IdentityTokenAuth, (qb) => {
qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.TOKEN_AUTH])).andOn(
`${TableName.Identity}.id`,
`${TableName.IdentityTokenAuth}.identityId`

@ -78,7 +78,10 @@ export const identityAwsAuthServiceFactory = ({
.map((accountId) => accountId.trim())
.some((accountId) => accountId === Account);
if (!isAccountAllowed) throw new UnauthorizedError();
if (!isAccountAllowed)
throw new ForbiddenRequestError({
message: "Access denied: AWS account ID not allowed."
});
}
if (identityAwsAuth.allowedPrincipalArns) {
@ -94,7 +97,10 @@ export const identityAwsAuthServiceFactory = ({
return regex.test(extractPrincipalArn(Arn));
});
if (!isArnAllowed) throw new UnauthorizedError();
if (!isArnAllowed)
throw new ForbiddenRequestError({
message: "Access denied: AWS principal ARN not allowed."
});
}
const identityAccessToken = await identityAwsAuthDAL.transaction(async (tx) => {

@ -17,6 +17,7 @@ export const validateAzureIdentity = async ({
const jwksUri = `https://login.microsoftonline.com/${tenantId}/discovery/keys`;
const decodedJwt = jwt.decode(azureJwt, { complete: true }) as TDecodedAzureAuthJwt;
const { kid } = decodedJwt.header;
const { data }: { data: TAzureJwksUriResponse } = await axios.get(jwksUri);
@ -27,6 +28,13 @@ export const validateAzureIdentity = async ({
const publicKey = `-----BEGIN CERTIFICATE-----\n${signingKey.x5c[0]}\n-----END CERTIFICATE-----`;
// Case: This can happen when the user uses a custom resource (such as https://management.azure.com&client_id=value).
// In this case, the audience in the decoded JWT will not have a trailing slash, but the resource will.
if (!decodedJwt.payload.aud.endsWith("/") && resource.endsWith("/")) {
// eslint-disable-next-line no-param-reassign
resource = resource.slice(0, -1);
}
return jwt.verify(azureJwt, publicKey, {
audience: resource,
issuer: `https://sts.windows.net/${tenantId}/`

@ -81,7 +81,10 @@ export const identityGcpAuthServiceFactory = ({
.map((serviceAccount) => serviceAccount.trim())
.some((serviceAccount) => serviceAccount === gcpIdentityDetails.email);
if (!isServiceAccountAllowed) throw new UnauthorizedError();
if (!isServiceAccountAllowed)
throw new ForbiddenRequestError({
message: "Access denied: GCP service account not allowed."
});
}
if (identityGcpAuth.type === "gce" && identityGcpAuth.allowedProjects && gcpIdentityDetails.computeEngineDetails) {
@ -92,7 +95,10 @@ export const identityGcpAuthServiceFactory = ({
.map((project) => project.trim())
.some((project) => project === gcpIdentityDetails.computeEngineDetails?.project_id);
if (!isProjectAllowed) throw new UnauthorizedError();
if (!isProjectAllowed)
throw new ForbiddenRequestError({
message: "Access denied: GCP project not allowed."
});
}
if (identityGcpAuth.type === "gce" && identityGcpAuth.allowedZones && gcpIdentityDetails.computeEngineDetails) {
@ -101,7 +107,10 @@ export const identityGcpAuthServiceFactory = ({
.map((zone) => zone.trim())
.some((zone) => zone === gcpIdentityDetails.computeEngineDetails?.zone);
if (!isZoneAllowed) throw new UnauthorizedError();
if (!isZoneAllowed)
throw new ForbiddenRequestError({
message: "Access denied: GCP zone not allowed."
});
}
const identityAccessToken = await identityGcpAuthDAL.transaction(async (tx) => {

@ -139,7 +139,10 @@ export const identityKubernetesAuthServiceFactory = ({
.map((namespace) => namespace.trim())
.some((namespace) => namespace === targetNamespace);
if (!isNamespaceAllowed) throw new UnauthorizedError();
if (!isNamespaceAllowed)
throw new ForbiddenRequestError({
message: "Access denied: K8s namespace not allowed."
});
}
if (identityKubernetesAuth.allowedNames) {
@ -150,7 +153,10 @@ export const identityKubernetesAuthServiceFactory = ({
.map((name) => name.trim())
.some((name) => name === targetName);
if (!isNameAllowed) throw new UnauthorizedError();
if (!isNameAllowed)
throw new ForbiddenRequestError({
message: "Access denied: K8s name not allowed."
});
}
if (identityKubernetesAuth.allowedAudience) {
@ -159,7 +165,10 @@ export const identityKubernetesAuthServiceFactory = ({
(audience) => audience === identityKubernetesAuth.allowedAudience
);
if (!isAudienceAllowed) throw new UnauthorizedError();
if (!isAudienceAllowed)
throw new ForbiddenRequestError({
message: "Access denied: K8s audience not allowed."
});
}
const identityAccessToken = await identityKubernetesAuthDAL.transaction(async (tx) => {

@ -124,13 +124,17 @@ export const identityOidcAuthServiceFactory = ({
if (identityOidcAuth.boundSubject) {
if (tokenData.sub !== identityOidcAuth.boundSubject) {
throw new UnauthorizedError();
throw new ForbiddenRequestError({
message: "Access denied: OIDC subject not allowed."
});
}
}
if (identityOidcAuth.boundAudiences) {
if (!identityOidcAuth.boundAudiences.split(", ").includes(tokenData.aud)) {
throw new UnauthorizedError();
throw new ForbiddenRequestError({
message: "Access denied: OIDC audience not allowed."
});
}
}
@ -139,7 +143,9 @@ export const identityOidcAuthServiceFactory = ({
const claimValue = (identityOidcAuth.boundClaims as Record<string, string>)[claimKey];
// handle both single and multi-valued claims
if (!claimValue.split(", ").some((claimEntry) => tokenData[claimKey] === claimEntry)) {
throw new UnauthorizedError();
throw new ForbiddenRequestError({
message: "Access denied: OIDC claim not allowed."
});
}
});
}

@ -574,14 +574,14 @@ export const integrationAuthServiceFactory = ({
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessId, accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
AWS.config.update({
const kms = new AWS.KMS({
region,
credentials: {
accessKeyId: String(accessId),
secretAccessKey: accessToken
}
});
const kms = new AWS.KMS();
const aliases = await kms.listAliases({}).promise();
const keyAliases = aliases.Aliases!.filter((alias) => {

@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TInternalKmsDALFactory = ReturnType<typeof internalKmsDALFactory>;
export const internalKmsDALFactory = (db: TDbClient) => {
const internalKmsOrm = ormify(db, TableName.InternalKms);
return internalKmsOrm;
};

@ -1,10 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TKmsDALFactory = ReturnType<typeof kmsDALFactory>;
export const kmsDALFactory = (db: TDbClient) => {
const kmsOrm = ormify(db, TableName.KmsKey);
return kmsOrm;
};

@ -0,0 +1,64 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { KmsKeysSchema, TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TKmsKeyDALFactory = ReturnType<typeof kmskeyDALFactory>;
export const kmskeyDALFactory = (db: TDbClient) => {
const kmsOrm = ormify(db, TableName.KmsKey);
const findByIdWithAssociatedKms = async (id: string, tx?: Knex) => {
try {
const result = await (tx || db.replicaNode())(TableName.KmsKey)
.where({ [`${TableName.KmsKey}.id` as "id"]: id })
.leftJoin(TableName.InternalKms, `${TableName.KmsKey}.id`, `${TableName.InternalKms}.kmsKeyId`)
.leftJoin(TableName.ExternalKms, `${TableName.KmsKey}.id`, `${TableName.ExternalKms}.kmsKeyId`)
.first()
.select(selectAllTableCols(TableName.KmsKey))
.select(
db.ref("id").withSchema(TableName.InternalKms).as("internalKmsId"),
db.ref("encryptedKey").withSchema(TableName.InternalKms).as("internalKmsEncryptedKey"),
db.ref("encryptionAlgorithm").withSchema(TableName.InternalKms).as("internalKmsEncryptionAlgorithm"),
db.ref("version").withSchema(TableName.InternalKms).as("internalKmsVersion"),
db.ref("id").withSchema(TableName.InternalKms).as("internalKmsId")
)
.select(
db.ref("id").withSchema(TableName.ExternalKms).as("externalKmsId"),
db.ref("provider").withSchema(TableName.ExternalKms).as("externalKmsProvider"),
db.ref("encryptedProviderInputs").withSchema(TableName.ExternalKms).as("externalKmsEncryptedProviderInput"),
db.ref("status").withSchema(TableName.ExternalKms).as("externalKmsStatus"),
db.ref("statusDetails").withSchema(TableName.ExternalKms).as("externalKmsStatusDetails")
);
const data = {
...KmsKeysSchema.parse(result),
isExternal: Boolean(result?.externalKmsId),
externalKms: result?.externalKmsId
? {
id: result.externalKmsId,
provider: result.externalKmsProvider,
encryptedProviderInput: result.externalKmsEncryptedProviderInput,
status: result.externalKmsStatus,
statusDetails: result.externalKmsStatusDetails
}
: undefined,
internalKms: result?.internalKmsId
? {
id: result.internalKmsId,
encryptedKey: result.internalKmsEncryptedKey,
encryptionAlgorithm: result.internalKmsEncryptionAlgorithm,
version: result.internalKmsVersion
}
: undefined
};
return data;
} catch (error) {
throw new DatabaseError({ error, name: "Find by id" });
}
};
return { ...kmsOrm, findByIdWithAssociatedKms };
};

@ -1,18 +1,34 @@
import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { randomSecureBytes } from "@app/lib/crypto";
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsDALFactory } from "./kms-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { TInternalKmsDALFactory } from "./internal-kms-dal";
import { TKmsKeyDALFactory } from "./kms-key-dal";
import { TKmsRootConfigDALFactory } from "./kms-root-config-dal";
import { TDecryptWithKmsDTO, TEncryptWithKmsDTO, TGenerateKMSDTO } from "./kms-types";
import {
TDecryptWithKeyDTO,
TDecryptWithKmsDTO,
TEncryptionWithKeyDTO,
TEncryptWithKmsDTO,
TGenerateKMSDTO
} from "./kms-types";
type TKmsServiceFactoryDep = {
kmsDAL: TKmsDALFactory;
kmsDAL: TKmsKeyDALFactory;
projectDAL: Pick<TProjectDALFactory, "findById" | "updateById" | "transaction">;
orgDAL: Pick<TOrgDALFactory, "findById" | "updateById" | "transaction">;
kmsRootConfigDAL: Pick<TKmsRootConfigDALFactory, "findById" | "create">;
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "waitTillReady" | "setItemWithExpiry">;
internalKmsDAL: Pick<TInternalKmsDALFactory, "create">;
};
export type TKmsServiceFactory = ReturnType<typeof kmsServiceFactory>;
@ -25,54 +41,161 @@ const KMS_ROOT_CREATION_WAIT_TIME = 10;
// akhilmhdh: Don't edit this value. This is measured for blob concatination in kms
const KMS_VERSION = "v01";
const KMS_VERSION_BLOB_LENGTH = 3;
export const kmsServiceFactory = ({ kmsDAL, kmsRootConfigDAL, keyStore }: TKmsServiceFactoryDep) => {
export const kmsServiceFactory = ({
kmsDAL,
kmsRootConfigDAL,
keyStore,
internalKmsDAL,
orgDAL,
projectDAL
}: TKmsServiceFactoryDep) => {
let ROOT_ENCRYPTION_KEY = Buffer.alloc(0);
// this is used symmetric encryption
const generateKmsKey = async ({ scopeId, scopeType, isReserved = true, tx }: TGenerateKMSDTO) => {
const generateKmsKey = async ({ orgId, isReserved = true, tx, slug }: TGenerateKMSDTO) => {
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const kmsKeyMaterial = randomSecureBytes(32);
const encryptedKeyMaterial = cipher.encrypt(kmsKeyMaterial, ROOT_ENCRYPTION_KEY);
const sanitizedSlug = slug ? slugify(slug) : slugify(alphaNumericNanoId(8).toLowerCase());
const dbQuery = async (db: Knex) => {
const kmsDoc = await kmsDAL.create(
{
slug: sanitizedSlug,
orgId,
isReserved
},
db
);
const { encryptedKey, ...doc } = await kmsDAL.create(
{
version: 1,
encryptedKey: encryptedKeyMaterial,
encryptionAlgorithm: SymmetricEncryption.AES_GCM_256,
isReserved,
orgId: scopeType === "org" ? scopeId : undefined,
projectId: scopeType === "project" ? scopeId : undefined
},
tx
);
await internalKmsDAL.create(
{
version: 1,
encryptedKey: encryptedKeyMaterial,
encryptionAlgorithm: SymmetricEncryption.AES_GCM_256,
kmsKeyId: kmsDoc.id
},
db
);
return kmsDoc;
};
if (tx) return dbQuery(tx);
const doc = await kmsDAL.transaction(async (tx2) => dbQuery(tx2));
return doc;
};
const encrypt = async ({ kmsId, plainText }: TEncryptWithKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const encryptWithKmsKey = async ({ kmsId }: Omit<TEncryptWithKmsDTO, "plainText">) => {
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
return ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
const kmsKey = cipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
const encryptedPlainTextBlob = cipher.encrypt(plainText, kmsKey);
const kmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY);
const encryptedPlainTextBlob = cipher.encrypt(plainText, kmsKey);
// Buffer#1 encrypted text + Buffer#2 version number
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
return { cipherTextBlob };
// Buffer#1 encrypted text + Buffer#2 version number
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
return { cipherTextBlob };
};
};
const decrypt = async ({ cipherTextBlob: versionedCipherTextBlob, kmsId }: TDecryptWithKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
const encryptWithInputKey = async ({ key }: Omit<TEncryptionWithKeyDTO, "plainText">) => {
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const kmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY);
return ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
const encryptedPlainTextBlob = cipher.encrypt(plainText, key);
// Buffer#1 encrypted text + Buffer#2 version number
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
return { cipherTextBlob };
};
};
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
const decryptedBlob = cipher.decrypt(cipherTextBlob, kmsKey);
return decryptedBlob;
const decryptWithKmsKey = async ({ kmsId }: Omit<TDecryptWithKmsDTO, "cipherTextBlob">) => {
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const kmsKey = cipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
return ({ cipherTextBlob: versionedCipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
const decryptedBlob = cipher.decrypt(cipherTextBlob, kmsKey);
return decryptedBlob;
};
};
const decryptWithInputKey = async ({ key }: Omit<TDecryptWithKeyDTO, "cipherTextBlob">) => {
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
return ({ cipherTextBlob: versionedCipherTextBlob }: Pick<TDecryptWithKeyDTO, "cipherTextBlob">) => {
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
const decryptedBlob = cipher.decrypt(cipherTextBlob, key);
return decryptedBlob;
};
};
const getOrgKmsKeyId = async (orgId: string) => {
const keyId = await orgDAL.transaction(async (tx) => {
const org = await orgDAL.findById(orgId, tx);
if (!org) {
throw new BadRequestError({ message: "Org not found" });
}
if (!org.kmsDefaultKeyId) {
// create default kms key for certificate service
const key = await generateKmsKey({
isReserved: true,
orgId: org.id,
tx
});
await orgDAL.updateById(
org.id,
{
kmsDefaultKeyId: key.id
},
tx
);
return key.id;
}
return org.kmsDefaultKeyId;
});
return keyId;
};
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
const keyId = await projectDAL.transaction(async (tx) => {
const project = await projectDAL.findById(projectId, tx);
if (!project) {
throw new BadRequestError({ message: "Project not found" });
}
if (!project.kmsSecretManagerKeyId) {
// create default kms key for certificate service
const key = await generateKmsKey({
isReserved: true,
orgId: project.orgId,
tx
});
await projectDAL.updateById(
projectId,
{
kmsSecretManagerKeyId: key.id
},
tx
);
return key.id;
}
return project.kmsSecretManagerKeyId;
});
return keyId;
};
const startService = async () => {
@ -123,7 +246,11 @@ export const kmsServiceFactory = ({ kmsDAL, kmsRootConfigDAL, keyStore }: TKmsSe
return {
startService,
generateKmsKey,
encrypt,
decrypt
encryptWithKmsKey,
encryptWithInputKey,
decryptWithKmsKey,
decryptWithInputKey,
getOrgKmsKeyId,
getProjectSecretManagerKmsKeyId
};
};

@ -1,9 +1,9 @@
import { Knex } from "knex";
export type TGenerateKMSDTO = {
scopeType: "project" | "org";
scopeId: string;
orgId: string;
isReserved?: boolean;
slug?: string;
tx?: Knex;
};
@ -12,7 +12,17 @@ export type TEncryptWithKmsDTO = {
plainText: Buffer;
};
export type TEncryptionWithKeyDTO = {
key: Buffer;
plainText: Buffer;
};
export type TDecryptWithKmsDTO = {
kmsId: string;
cipherTextBlob: Buffer;
};
export type TDecryptWithKeyDTO = {
key: Buffer;
cipherTextBlob: Buffer;
};

@ -74,6 +74,7 @@ export const orgDALFactory = (db: TDbClient) => {
db.ref("role").withSchema(TableName.OrgMembership),
db.ref("roleId").withSchema(TableName.OrgMembership),
db.ref("status").withSchema(TableName.OrgMembership),
db.ref("isActive").withSchema(TableName.OrgMembership),
db.ref("email").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users),
@ -207,9 +208,9 @@ export const orgDALFactory = (db: TDbClient) => {
}
};
const updateById = async (orgId: string, data: Partial<TOrganizations>) => {
const updateById = async (orgId: string, data: Partial<TOrganizations>, tx?: Knex) => {
try {
const [org] = await db(TableName.Organization)
const [org] = await (tx || db)(TableName.Organization)
.where({ id: orgId })
.update({ ...data })
.returning("*");

@ -144,10 +144,7 @@ export const orgServiceFactory = ({
return members;
};
const findAllWorkspaces = async ({ actor, actorId, actorOrgId, actorAuthMethod, orgId }: TFindAllWorkspacesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
const findAllWorkspaces = async ({ actor, actorId, orgId }: TFindAllWorkspacesDTO) => {
const organizationWorkspaceIds = new Set((await projectDAL.find({ orgId })).map((workspace) => workspace.id));
let workspaces: (TProjects & { organization: string } & {
@ -207,7 +204,8 @@ export const orgServiceFactory = ({
orgId,
userId: user.id,
role: OrgMembershipRole.Admin,
status: OrgMembershipStatus.Accepted
status: OrgMembershipStatus.Accepted,
isActive: true
};
await orgDAL.createMembership(createMembershipData, tx);
@ -311,7 +309,8 @@ export const orgServiceFactory = ({
userId,
orgId: org.id,
role: OrgMembershipRole.Admin,
status: OrgMembershipStatus.Accepted
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
@ -460,7 +459,8 @@ export const orgServiceFactory = ({
inviteEmail: inviteeEmail,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
status: OrgMembershipStatus.Invited,
isActive: true
},
tx
);
@ -491,7 +491,8 @@ export const orgServiceFactory = ({
orgId,
userId: user.id,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
status: OrgMembershipStatus.Invited,
isActive: true
},
tx
);

@ -71,9 +71,8 @@ export const getProjectKmsCertificateKeyId = async ({
if (!project.kmsCertificateKeyId) {
// create default kms key for certificate service
const key = await kmsService.generateKmsKey({
scopeId: projectId,
scopeType: "project",
isReserved: true,
orgId: project.orgId,
tx
});

@ -322,7 +322,7 @@ export const secretFolderDALFactory = (db: TDbClient) => {
.first();
if (folder) {
const { envId, envName, envSlug, ...el } = folder;
return { ...el, environment: { envId, envName, envSlug } };
return { ...el, environment: { envId, envName, envSlug }, envId };
}
} catch (error) {
throw new DatabaseError({ error, name: "Find by id" });

@ -6,7 +6,7 @@ import { TSecretFoldersInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { BadRequestError } from "@app/lib/errors";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
@ -14,6 +14,7 @@ import { TSecretFolderDALFactory } from "./secret-folder-dal";
import {
TCreateFolderDTO,
TDeleteFolderDTO,
TGetFolderByIdDTO,
TGetFolderDTO,
TUpdateFolderDTO,
TUpdateManyFoldersDTO
@ -368,11 +369,22 @@ export const secretFolderServiceFactory = ({
return folders;
};
const getFolderById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TGetFolderByIdDTO) => {
const folder = await folderDAL.findById(id);
if (!folder) throw new NotFoundError({ message: "folder not found" });
// folder list is allowed to be read by anyone
// permission to check does user has access
await permissionService.getProjectPermission(actor, actorId, folder.projectId, actorAuthMethod, actorOrgId);
return folder;
};
return {
createFolder,
updateFolder,
updateManyFolders,
deleteFolder,
getFolders
getFolders,
getFolderById
};
};

@ -37,3 +37,7 @@ export type TGetFolderDTO = {
environment: string;
path: string;
} & TProjectPermission;
export type TGetFolderByIdDTO = {
id: string;
} & Omit<TProjectPermission, "projectId">;

@ -1,9 +1,9 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { TableName, TSecretSharing } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TSecretSharingDALFactory = ReturnType<typeof secretSharingDALFactory>;
@ -13,15 +13,58 @@ export const secretSharingDALFactory = (db: TDbClient) => {
const pruneExpiredSharedSecrets = async (tx?: Knex) => {
try {
const today = new Date();
const docs = await (tx || db)(TableName.SecretSharing).where("expiresAt", "<", today).del();
const docs = await (tx || db)(TableName.SecretSharing)
.where("expiresAt", "<", today)
.andWhere("encryptedValue", "<>", "")
.update({
encryptedValue: "",
tag: "",
iv: "",
hashedHex: ""
});
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "pruneExpiredSharedSecrets" });
}
};
const findActiveSharedSecrets = async (filters: Partial<TSecretSharing>, tx?: Knex) => {
try {
const now = new Date();
return await (tx || db)(TableName.SecretSharing)
.where(filters)
.andWhere("expiresAt", ">", now)
.andWhere("encryptedValue", "<>", "")
.select(selectAllTableCols(TableName.SecretSharing))
.orderBy("expiresAt", "asc");
} catch (error) {
throw new DatabaseError({
error,
name: "Find Active Shared Secrets"
});
}
};
const softDeleteById = async (id: string) => {
try {
await sharedSecretOrm.updateById(id, {
encryptedValue: "",
iv: "",
tag: "",
hashedHex: ""
});
} catch (error) {
throw new DatabaseError({
error,
name: "Soft Delete Shared Secret"
});
}
};
return {
...sharedSecretOrm,
pruneExpiredSharedSecrets
pruneExpiredSharedSecrets,
softDeleteById,
findActiveSharedSecrets
};
};

@ -101,7 +101,7 @@ export const secretSharingServiceFactory = ({
const { actor, actorId, orgId, actorAuthMethod, actorOrgId } = getSharedSecretsInput;
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
if (!permission) throw new UnauthorizedError({ name: "User not in org" });
const userSharedSecrets = await secretSharingDAL.find({ userId: actorId, orgId }, { sort: [["expiresAt", "asc"]] });
const userSharedSecrets = await secretSharingDAL.findActiveSharedSecrets({ userId: actorId, orgId });
return userSharedSecrets;
};
@ -113,7 +113,7 @@ export const secretSharingServiceFactory = ({
}
if (sharedSecret.expiresAfterViews != null && sharedSecret.expiresAfterViews >= 0) {
if (sharedSecret.expiresAfterViews === 0) {
await secretSharingDAL.deleteById(sharedSecretId);
await secretSharingDAL.softDeleteById(sharedSecretId);
return;
}
await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } });

@ -642,7 +642,7 @@ export const secretQueueFactory = ({
});
queueService.start(QueueName.SecretWebhook, async (job) => {
await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL });
await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL, projectDAL });
});
return {

@ -1,6 +1,7 @@
import bcrypt from "bcrypt";
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
@ -20,6 +21,7 @@ type TSuperAdminServiceFactoryDep = {
authService: Pick<TAuthLoginFactory, "generateUserTokens">;
orgService: Pick<TOrgServiceFactory, "createOrganization">;
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem">;
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures">;
};
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
@ -36,7 +38,8 @@ export const superAdminServiceFactory = ({
userDAL,
authService,
orgService,
keyStore
keyStore,
licenseService
}: TSuperAdminServiceFactoryDep) => {
const initServerCfg = async () => {
// TODO(akhilmhdh): bad pattern time less change this later to me itself
@ -219,6 +222,12 @@ export const superAdminServiceFactory = ({
};
const deleteUser = async (userId: string) => {
if (!licenseService.onPremFeatures?.instanceUserManagement) {
throw new BadRequestError({
message: "Failed to delete user due to plan restriction. Upgrade to Infisical's Pro plan."
});
}
const user = await userDAL.deleteById(userId);
return user;
};

@ -9,6 +9,7 @@ import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TWebhookDALFactory } from "./webhook-dal";
import { WebhookType } from "./webhook-types";
@ -66,11 +67,16 @@ export const triggerWebhookRequest = async (webhook: TWebhooks, data: Record<str
export const getWebhookPayload = (
eventName: string,
workspaceId: string,
environment: string,
secretPath?: string,
type?: string | null
details: {
workspaceName: string;
workspaceId: string;
environment: string;
secretPath?: string;
type?: string | null;
}
) => {
const { workspaceName, workspaceId, environment, secretPath, type } = details;
switch (type) {
case WebhookType.SLACK:
return {
@ -80,8 +86,8 @@ export const getWebhookPayload = (
color: "#E7F256",
fields: [
{
title: "Workspace ID",
value: workspaceId,
title: "Project",
value: workspaceName,
short: false
},
{
@ -117,7 +123,9 @@ export type TFnTriggerWebhookDTO = {
environment: string;
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "transaction" | "update" | "bulkUpdate">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findById">;
};
// this is reusable function
// used in secret queue to trigger webhook and update status when secrets changes
export const fnTriggerWebhook = async ({
@ -125,7 +133,8 @@ export const fnTriggerWebhook = async ({
secretPath,
projectId,
webhookDAL,
projectEnvDAL
projectEnvDAL,
projectDAL
}: TFnTriggerWebhookDTO) => {
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment);
const toBeTriggeredHooks = webhooks.filter(
@ -134,9 +143,19 @@ export const fnTriggerWebhook = async ({
);
if (!toBeTriggeredHooks.length) return;
logger.info("Secret webhook job started", { environment, secretPath, projectId });
const project = await projectDAL.findById(projectId);
const webhooksTriggered = await Promise.allSettled(
toBeTriggeredHooks.map((hook) =>
triggerWebhookRequest(hook, getWebhookPayload("secrets.modified", projectId, environment, secretPath, hook.type))
triggerWebhookRequest(
hook,
getWebhookPayload("secrets.modified", {
workspaceName: project.name,
workspaceId: projectId,
environment,
secretPath,
type: hook.type
})
)
)
);

@ -6,6 +6,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TWebhookDALFactory } from "./webhook-dal";
import { decryptWebhookDetails, getWebhookPayload, triggerWebhookRequest } from "./webhook-fns";
@ -20,12 +21,18 @@ import {
type TWebhookServiceFactoryDep = {
webhookDAL: TWebhookDALFactory;
projectEnvDAL: TProjectEnvDALFactory;
projectDAL: Pick<TProjectDALFactory, "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
export type TWebhookServiceFactory = ReturnType<typeof webhookServiceFactory>;
export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionService }: TWebhookServiceFactoryDep) => {
export const webhookServiceFactory = ({
webhookDAL,
projectEnvDAL,
permissionService,
projectDAL
}: TWebhookServiceFactoryDep) => {
const createWebhook = async ({
actor,
actorId,
@ -124,13 +131,21 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
const project = await projectDAL.findById(webhook.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
let webhookError: string | undefined;
try {
await triggerWebhookRequest(
webhook,
getWebhookPayload("test", webhook.projectId, webhook.environment.slug, webhook.secretPath, webhook.type)
getWebhookPayload("test", {
workspaceName: project.name,
workspaceId: webhook.projectId,
environment: webhook.environment.slug,
secretPath: webhook.secretPath,
type: webhook.type
})
);
} catch (err) {
webhookError = (err as Error).message;

@ -10,7 +10,7 @@ require (
github.com/fatih/semgroup v1.2.0
github.com/gitleaks/go-gitdiff v0.8.0
github.com/h2non/filetype v1.1.3
github.com/infisical/go-sdk v0.2.0
github.com/infisical/go-sdk v0.3.0
github.com/mattn/go-isatty v0.0.14
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
github.com/muesli/mango-cobra v1.2.0
@ -19,7 +19,7 @@ require (
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a
github.com/rs/cors v1.9.0
github.com/rs/cors v1.11.0
github.com/rs/zerolog v1.26.1
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.8.1
@ -52,7 +52,7 @@ require (
github.com/chzyer/readline v1.5.1 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-logr/logr v1.4.1 // indirect

@ -117,8 +117,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -263,8 +263,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/infisical/go-sdk v0.2.0 h1:n1/KNdYpeQavSqVwC9BfeV8VRzf3N2X9zO1tzQOSj5Q=
github.com/infisical/go-sdk v0.2.0/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
github.com/infisical/go-sdk v0.3.0 h1:Ls71t227F4CWVQWdStcwv8WDyfHe8eRlyAuMRNHsmlQ=
github.com/infisical/go-sdk v0.3.0/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -356,8 +356,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=

@ -122,6 +122,21 @@ func handleAwsIamAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.Infi
return infisicalClient.Auth().AwsIamAuthLogin(identityId)
}
func handleOidcAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.InfisicalClientInterface) (credential infisicalSdk.MachineIdentityCredential, e error) {
identityId, err := util.GetCmdFlagOrEnv(cmd, "machine-identity-id", util.INFISICAL_MACHINE_IDENTITY_ID_NAME)
if err != nil {
return infisicalSdk.MachineIdentityCredential{}, err
}
jwt, err := util.GetCmdFlagOrEnv(cmd, "oidc-jwt", util.INFISICAL_OIDC_AUTH_JWT_NAME)
if err != nil {
return infisicalSdk.MachineIdentityCredential{}, err
}
return infisicalClient.Auth().OidcAuthLogin(identityId, jwt)
}
func formatAuthMethod(authMethod string) string {
return strings.ReplaceAll(authMethod, "-", " ")
}
@ -257,6 +272,7 @@ var loginCmd = &cobra.Command{
util.AuthStrategy.GCP_ID_TOKEN_AUTH: handleGcpIdTokenAuthLogin,
util.AuthStrategy.GCP_IAM_AUTH: handleGcpIamAuthLogin,
util.AuthStrategy.AWS_IAM_AUTH: handleAwsIamAuthLogin,
util.AuthStrategy.OIDC_AUTH: handleOidcAuthLogin,
}
credential, err := authStrategies[strategy](cmd, infisicalClient)
@ -456,6 +472,7 @@ func init() {
loginCmd.Flags().String("machine-identity-id", "", "machine identity id for kubernetes, azure, gcp-id-token, gcp-iam, and aws-iam auth methods")
loginCmd.Flags().String("service-account-token-path", "", "service account token path for kubernetes auth")
loginCmd.Flags().String("service-account-key-file-path", "", "service account key file path for GCP IAM auth")
loginCmd.Flags().String("oidc-jwt", "", "JWT for OIDC authentication")
}
func DomainOverridePrompt() (bool, error) {
@ -616,7 +633,7 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R
loginTwoResponseResult, err := api.CallLogin2V2(httpClient, api.GetLoginTwoV2Request{
Email: email,
ClientProof: hex.EncodeToString(srpM1),
Password: password,
Password: password,
})
if err != nil {

@ -9,6 +9,7 @@ var AuthStrategy = struct {
GCP_ID_TOKEN_AUTH AuthStrategyType
GCP_IAM_AUTH AuthStrategyType
AWS_IAM_AUTH AuthStrategyType
OIDC_AUTH AuthStrategyType
}{
UNIVERSAL_AUTH: "universal-auth",
KUBERNETES_AUTH: "kubernetes",
@ -16,6 +17,7 @@ var AuthStrategy = struct {
GCP_ID_TOKEN_AUTH: "gcp-id-token",
GCP_IAM_AUTH: "gcp-iam",
AWS_IAM_AUTH: "aws-iam",
OIDC_AUTH: "oidc-auth",
}
var AVAILABLE_AUTH_STRATEGIES = []AuthStrategyType{
@ -25,6 +27,7 @@ var AVAILABLE_AUTH_STRATEGIES = []AuthStrategyType{
AuthStrategy.GCP_ID_TOKEN_AUTH,
AuthStrategy.GCP_IAM_AUTH,
AuthStrategy.AWS_IAM_AUTH,
AuthStrategy.OIDC_AUTH,
}
func IsAuthMethodValid(authMethod string, allowUserAuth bool) (isValid bool, strategy AuthStrategyType) {

@ -19,6 +19,9 @@ const (
// GCP Auth
INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH_NAME = "INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH"
// OIDC Auth
INFISICAL_OIDC_AUTH_JWT_NAME = "INFISICAL_OIDC_AUTH_JWT"
// Generic env variable used for auth methods that require a machine identity ID
INFISICAL_MACHINE_IDENTITY_ID_NAME = "INFISICAL_MACHINE_IDENTITY_ID"

@ -0,0 +1,50 @@
---
title: "Hiring"
sidebarTitle: "Hiring"
description: "The guide to hiring at Infisical."
---
Infisical is actively growing and we are hiring for many positions at any given time. This page describes some details of the hiring process we have.
## Strategy
Infisical recruitment strategy relies on 100% inbound interest by default. Many of our team members have previously used Infisical or contributed to our [open source project](https://github.com/Infisical/infisical). This allows us to hire the best candidates who are most interested in working at Infisical.
## Geography
Infisical is a remote-first company, and we have team members across the whole globe. That being said, there are some legal and accounting limitations that we need to abide by. As a result, we are currently only open to hiring from the following countries:
- Australia
- Austria
- Belgium
- Brazil
- Canada
- Chile
- Costa Rica
- Denmark
- Finland
- France
- Germany
- India
- Ireland
- Israel
- Italy
- Japan
- Kenya
- Latvia
- Luxembourg
- Mexico
- Netherlands
- New Zealand
- Philippines
- Poland
- Portugal
- Singapore
- South Africa
- South Korea
- Spain
- Switzerland
- Sweden
- UAE
- United Kingdom
- United States

@ -58,7 +58,8 @@
"pages": [
"handbook/onboarding",
"handbook/spending-money",
"handbook/time-off"
"handbook/time-off",
"handbook/hiring"
]
}
],

@ -0,0 +1,4 @@
---
title: "Get by ID"
openapi: "GET /api/v1/folders/{id}"
---

@ -8,7 +8,8 @@ infisical login
```
### Description
The CLI uses authentication to verify your identity. When you enter the correct email and password for your account, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI.
The CLI uses authentication to verify your identity. When you enter the correct email and password for your account, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI.
To change where the login credentials are stored, visit the [vaults command](./vault).
@ -17,12 +18,12 @@ If you have added multiple users, you can switch between the users by using the
<Info>
When you authenticate with **any other method than `user`**, an access token will be printed to the console upon successful login. This token can be used to authenticate with the Infisical API and the CLI by passing it in the `--token` flag when applicable.
Use flag `--plain` along with `--silent` to print only the token in plain text when using a machine identity auth method.
Use flag `--plain` along with `--silent` to print only the token in plain text when using a machine identity auth method.
</Info>
### Flags
The login command supports a number of flags that you can use for different authentication methods. Below is a list of all the flags that can be used with the login command.
<AccordionGroup>
@ -52,6 +53,7 @@ The login command supports a number of flags that you can use for different auth
<Tip>
The `client-id` flag can be substituted with the `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` environment variable.
</Tip>
</Accordion>
<Accordion title="--client-secret">
```bash
@ -63,6 +65,7 @@ The login command supports a number of flags that you can use for different auth
<Tip>
The `client-secret` flag can be substituted with the `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` environment variable.
</Tip>
</Accordion>
<Accordion title="--machine-identity-id">
```bash
@ -75,6 +78,7 @@ The login command supports a number of flags that you can use for different auth
<Tip>
The `machine-identity-id` flag can be substituted with the `INFISICAL_MACHINE_IDENTITY_ID` environment variable.
</Tip>
</Accordion>
<Accordion title="--service-account-token-path">
```bash
@ -88,6 +92,7 @@ The login command supports a number of flags that you can use for different auth
<Tip>
The `service-account-token-path` flag can be substituted with the `INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH` environment variable.
</Tip>
</Accordion>
<Accordion title="--service-account-key-file-path">
```bash
@ -100,9 +105,23 @@ The login command supports a number of flags that you can use for different auth
<Tip>
The `service-account-key-path` flag can be substituted with the `INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH` environment variable.
</Tip>
</Accordion>
</AccordionGroup>
<Accordion title="--oidc-jwt">
```bash
infisical login --oidc-jwt=<oidc-jwt-token>
```
#### Description
The JWT provided by an identity provider for OIDC authentication.
<Tip>
The `oidc-jwt` flag can be substituted with the `INFISICAL_OIDC_AUTH_JWT` environment variable.
</Tip>
</Accordion>
### Authentication Methods
@ -121,6 +140,7 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
Your machine identity client secret.
</ParamField>
</Expandable>
</ParamField>
<Steps>
@ -134,6 +154,7 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
infisical login --method=universal-auth --client-id=<client-id> --client-secret=<client-secret>
```
</Step>
</Steps>
</Accordion>
<Accordion title="Native Kubernetes">
@ -148,6 +169,7 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
Path to the Kubernetes service account token to use. Default: `/var/run/secrets/kubernetes.io/serviceaccount/token`.
</ParamField>
</Expandable>
</ParamField>
<Steps>
@ -162,6 +184,7 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
infisical login --method=kubernetes --machine-identity-id=<machine-identity-id> --service-account-token-path=<service-account-token-path>
```
</Step>
</Steps>
</Accordion>
@ -213,6 +236,7 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
```
</Step>
</Steps>
</Accordion>
<Accordion title="GCP IAM">
The GCP IAM method is used to authenticate with Infisical with a GCP service account key.
@ -235,11 +259,12 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
<Step title="Obtain an access token">
Run the `login` command with the following flags to obtain an access token:
```bash
```bash
infisical login --method=gcp-iam --machine-identity-id=<machine-identity-id> --service-account-key-file-path=<service-account-key-file-path>
```
</Step>
</Steps>
</Accordion>
<Accordion title="Native AWS IAM">
The AWS IAM method is used to authenticate with Infisical with an AWS IAM role while running in an AWS environment like EC2, Lambda, etc.
@ -264,10 +289,40 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
```
</Step>
</Steps>
</Accordion>
<Accordion title="OIDC Auth">
The OIDC Auth method is used to authenticate with Infisical via identity tokens with OIDC.
<ParamField query="Flags">
<Expandable title="properties">
<ParamField query="machine-identity-id" type="string" required>
Your machine identity ID.
</ParamField>
<ParamField query="oidc-jwt" type="string" required>
The OIDC JWT from the identity provider.
</ParamField>
</Expandable>
</ParamField>
<Steps>
<Step title="Create an OIDC machine identity">
To create an OIDC machine identity, follow the step by step guide outlined [here](/documentation/platform/identities/oidc-auth/general).
</Step>
<Step title="Obtain an access token">
Run the `login` command with the following flags to obtain an access token:
```bash
infisical login --method=oidc-auth --machine-identity-id=<machine-identity-id> --oidc-jwt=<oidc-jwt>
```
</Step>
</Steps>
</Accordion>
</AccordionGroup>
### Machine Identity Authentication Quick Start
In this example we'll be using the `universal-auth` method to login to obtain an Infisical access token, which we will then use to fetch secrets with.
<Steps>
@ -277,8 +332,8 @@ In this example we'll be using the `universal-auth` method to login to obtain an
```
Now that we've set the `INFISICAL_TOKEN` environment variable, we can use the CLI to interact with Infisical. The CLI will automatically check for the presence of the `INFISICAL_TOKEN` environment variable and use it for authentication.
Alternatively, if you would rather use the `--token` flag to pass the token directly, you can do so by running the following command:
```bash
@ -297,6 +352,7 @@ In this example we'll be using the `universal-auth` method to login to obtain an
The `--recursive`, and `--env` flag is optional and will fetch all secrets in subfolders. The default environment is `dev` if no `--env` flag is provided.
</Info>
</Step>
</Steps>
And that's it! Now you're ready to start using the Infisical CLI to interact with your secrets, with the use of Machine Identities.

@ -1,5 +1,5 @@
---
title: OIDC Auth
title: General
description: "Learn how to authenticate with Infisical from any platform or environment using OpenID Connect (OIDC)."
---
@ -7,7 +7,7 @@ description: "Learn how to authenticate with Infisical from any platform or envi
## Diagram
The following sequence digram illustrates the OIDC Auth workflow for authenticating clients with Infisical.
The following sequence diagram illustrates the OIDC Auth workflow for authenticating clients with Infisical.
```mermaid
sequenceDiagram
@ -83,7 +83,7 @@ In the following steps, we explore how to create and use identities to access th
<Tip>Restrict access by configuring the Subject, Audiences, and Claims fields</Tip>
Here's some more guidance on each field:
- OIDC Discovery URL: The URL used to retrieve the OpenID Connect configuration information from the identity provider. This will be used to fetch the public key needed for verifying the provided JWT.
- OIDC Discovery URL: The URL used to retrieve the OpenID Connect configuration from the identity provider. This will be used to fetch the public key needed for verifying the provided JWT.
- Issuer: The unique identifier of the identity provider issuing the JWT. This value is used to verify the iss (issuer) claim in the JWT to ensure the token is issued by a trusted provider.
- CA Certificate: The PEM-encoded CA cert for establishing secure communication with the Identity Provider endpoints.
- Subject: The expected principal that is the subject of the JWT. The `sub` (subject) claim in the JWT should match this value.

@ -0,0 +1,170 @@
---
title: Github
description: "Learn how to authenticate Github workflows with Infisical using OpenID Connect (OIDC)."
---
**OIDC Auth** is a platform-agnostic JWT-based authentication method that can be used to authenticate from any platform or environment using an identity provider with OpenID Connect.
## Diagram
The following sequence diagram illustrates the OIDC Auth workflow for authenticating Github workflows with Infisical.
```mermaid
sequenceDiagram
participant Client as Github Workflow
participant Idp as Identity Provider
participant Infis as Infisical
Client->>Idp: Step 1: Request identity token
Idp-->>Client: Return JWT with verifiable claims
Note over Client,Infis: Step 2: Login Operation
Client->>Infis: Send signed JWT to /api/v1/auth/oidc-auth/login
Note over Infis,Idp: Step 3: Query verification
Infis->>Idp: Request JWT public key using OIDC Discovery
Idp-->>Infis: Return public key
Note over Infis: Step 4: JWT validation
Infis->>Client: Return short-lived access token
Note over Client,Infis: Step 5: Access Infisical API with Token
Client->>Infis: Make authenticated requests using the short-lived access token
```
## Concept
At a high-level, Infisical authenticates a client by verifying the JWT and checking that it meets specific requirements (e.g. it is issued by a trusted identity provider) at the `/api/v1/auth/oidc-auth/login` endpoint. If successful,
then Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
To be more specific:
1. The Github workflow requests an identity token from Github's identity provider.
2. The fetched identity token is sent to Infisical at the `/api/v1/auth/oidc-auth/login` endpoint.
3. Infisical fetches the public key that was used to sign the identity token from Github's identity provider using OIDC Discovery.
4. Infisical validates the JWT using the public key provided by the identity provider and checks that the subject, audience, and claims of the token matches with the set criteria.
5. If all is well, Infisical returns a short-lived access token that the Github workflow can use to make authenticated requests to the Infisical API.
<Note>
Infisical needs network-level access to Github's identity provider endpoints.
</Note>
## Guide
In the following steps, we explore how to create and use identities to access the Infisical API using the OIDC Auth authentication method.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)
When creating an identity, you specify an organization level [role](/documentation/platform/role-based-access-controls) for it to assume; you can configure roles in Organization Settings > Access Control > Organization Roles.
![identities organization create](/images/platform/identities/identities-org-create.png)
Now input a few details for your new identity. Here's some guidance for each field:
- Name (required): A friendly name for the identity.
- Role (required): A role from the **Organization Roles** tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to.
Once you've created an identity, you'll be redirected to a page where you can manage the identity.
![identities page](/images/platform/identities/identities-page.png)
Since the identity has been configured with Universal Auth by default, you should re-configure it to use OIDC Auth instead. To do this, press to edit the **Authentication** section,
remove the existing Universal Auth configuration, and add a new OIDC Auth configuration onto the identity.
![identities page remove default auth](/images/platform/identities/identities-page-remove-default-auth.png)
![identities create oidc auth method](/images/platform/identities/identities-org-create-oidc-auth-method.png)
<Warning>Restrict access by configuring the Subject, Audiences, and Claims fields</Warning>
Here's some more guidance on each field:
- OIDC Discovery URL: The URL used to retrieve the OpenID Connect configuration from the identity provider. This will be used to fetch the public key needed for verifying the provided JWT. This should be set to `https://token.actions.githubusercontent.com`
- Issuer: The unique identifier of the identity provider issuing the JWT. This value is used to verify the iss (issuer) claim in the JWT to ensure the token is issued by a trusted provider. This should be set to `https://token.actions.githubusercontent.com`
- CA Certificate: The PEM-encoded CA cert for establishing secure communication with the Identity Provider endpoints. For Github workflows, this can be left as blank.
- Subject: The expected principal that is the subject of the JWT. The format of the sub field for GitHub workflow OIDC tokens is as follows: `"repo:<owner>/<repo>:<environment>"`. The environment can be where the GitHub workflow is running, such as `environment`, `ref`, or `job_workflow_ref`. For example, if you have a repository owned by octocat named example-repo, and the GitHub workflow is running on the main branch, the subject field might look like this: `repo:octocat/example-repo:ref:refs/heads/main`
- Audiences: A list of intended recipients. This value is checked against the aud (audience) claim in the token. By default, set this to the URL of the repository owner, such as the organization that owns the repository (e.g. `https://github.com/octo-org`).
- Claims: Additional information or attributes that should be present in the JWT for it to be valid. You can refer to Github's [documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token) for the complete list of supported claims.
- Access Token TTL (default is `2592000` equivalent to 30 days): The lifetime for an acccess token in seconds. This value will be referenced at renewal time.
- Access Token Max TTL (default is `2592000` equivalent to 30 days): The maximum lifetime for an acccess token in seconds. This value will be referenced at renewal time.
- Access Token Max Number of Uses (default is `0`): The maximum number of times that an access token can be used; a value of `0` implies infinite number of uses.
- Access Token Trusted IPs: The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the `0.0.0.0/0`, allowing usage from any network address.
<Tip>If you are unsure about what to configure for the subject, audience, and claims fields you can use [github/actions-oidc-debugger](https://github.com/github/actions-oidc-debugger) to get the appropriate values. Alternatively, you can fetch the JWT from the workflow and inspect the fields manually.</Tip>
</Step>
<Step title="Adding an identity to a project">
To enable the identity to access project-level resources such as secrets within a specific project, you should add it to that project.
To do this, head over to the project you want to add the identity to and go to Project Settings > Access Control > Machine Identities and press **Add identity**.
Next, select the identity you want to add to the project and the project level role you want to allow it to assume. The project role assigned will determine what project level resources this identity can have access to.
![identities project](/images/platform/identities/identities-project.png)
![identities project create](/images/platform/identities/identities-project-create.png)
</Step>
<Step title="Accessing the Infisical API with the identity">
As a prerequisite, you will need to set `id-token:write` permissions for the Github workflow. This setting allows the JWT to be requested from Github's OIDC provider.
```yaml
permissions:
id-token: write # This is required for requesting the JWT
...
```
To access the Infisical API as the identity, you need to fetch an identity token from Github's identity provider and make a request to the `/api/v1/auth/oidc-auth/login` endpoint in exchange for an access token.
The identity token can be fetched using either of the following approaches:
- Using environment variables on the runner (`ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN`).
```yaml
steps:
- name: Request OIDC Token
run: |
echo "Requesting OIDC token..."
TOKEN=$(curl -s -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r '.value')
echo "TOKEN=$TOKEN" >> $GITHUB_ENV
```
- Using `getIDToken()` from the Github Actions toolkit.
Below is an example of how a Github workflow can be configured to fetch secrets from Infisical using the [Infisical Secrets Action](https://github.com/Infisical/secrets-action) with OIDC Auth.
```yaml
name: Manual workflow
on:
workflow_dispatch:
permissions:
id-token: write # This is required for requesting the JWT
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: Infisical/secrets-action@v1.0.7
with:
method: "oidc"
env-slug: "dev"
project-slug: "ggggg-9-des"
identity-id: "6b579c00-5c85-4b44-aabe-f8a
...
```
Preceding steps can then use the secret values injected onto the workflow's environment.
<Tip>
We recommend using [Infisical Secrets Action](https://github.com/Infisical/secrets-action) to authenticate with Infisical using OIDC Auth as it handles the authentication process including the fetching of identity tokens for you.
</Tip>
<Note>
Each identity access token has a time-to-live (TLL) which you can infer from the response of the login operation;
the default TTL is `7200` seconds which can be adjusted.
If an identity access token expires, it can no longer authenticate with the Infisical API. In this case,
a new access token should be obtained by performing another login operation.
</Note>
</Step>
</Steps>

@ -122,6 +122,7 @@ spec:
# Azure Auth
azureAuth:
identityId: <your-machine-identity-id>
resource: https://management.azure.com/&client_id=CLIENT_ID # (Optional) This is the Azure resource that you want to access. For example, "https://management.azure.com/". If no value is provided, it will default to "https://management.azure.com/"
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
secretsScope:

@ -168,7 +168,13 @@
"documentation/platform/identities/gcp-auth",
"documentation/platform/identities/azure-auth",
"documentation/platform/identities/aws-auth",
"documentation/platform/identities/oidc-auth",
{
"group": "OIDC Auth",
"pages": [
"documentation/platform/identities/oidc-auth/general",
"documentation/platform/identities/oidc-auth/github"
]
},
"documentation/platform/mfa",
{
"group": "SSO",
@ -570,6 +576,7 @@
"group": "Folders",
"pages": [
"api-reference/endpoints/folders/list",
"api-reference/endpoints/folders/get-by-id",
"api-reference/endpoints/folders/create",
"api-reference/endpoints/folders/update",
"api-reference/endpoints/folders/delete"

@ -47,7 +47,7 @@
"@ucast/mongo2js": "^1.3.4",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.27.2",
"axios": "^0.28.0",
"axios-auth-refresh": "^3.3.6",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
@ -136,10 +136,10 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^8.0.0",
"eslint-plugin-storybook": "^0.6.12",
"postcss": "^8.4.14",
"postcss": "^8.4.39",
"prettier": "^2.8.3",
"prettier-plugin-tailwindcss": "^0.2.2",
"storybook": "^7.5.2",
"storybook": "^7.6.20",
"storybook-dark-mode": "^3.0.0",
"tailwindcss": "3.2",
"typescript": "^4.9.3"
@ -6200,15 +6200,15 @@
}
},
"node_modules/@storybook/builder-manager": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.19.tgz",
"integrity": "sha512-Dt5OLh97xeWh4h2mk9uG0SbCxBKHPhIiHLHAKEIDzIZBdwUhuyncVNDPHW2NlXM+S7U0/iKs2tw05waqh2lHvg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.20.tgz",
"integrity": "sha512-e2GzpjLaw6CM/XSmc4qJRzBF8GOoOyotyu3JrSPTYOt4RD8kjUsK4QlismQM1DQRu8i39aIexxmRbiJyD74xzQ==",
"dev": true,
"dependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@storybook/core-common": "7.6.19",
"@storybook/manager": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/core-common": "7.6.20",
"@storybook/manager": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@types/ejs": "^3.1.1",
"@types/find-cache-dir": "^3.2.1",
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10",
@ -6228,13 +6228,13 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/channels": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/global": "^5.0.0",
"qs": "^6.10.0",
"telejson": "^7.2.0",
@ -6246,9 +6246,9 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/client-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
"dev": true,
"dependencies": {
"@storybook/global": "^5.0.0"
@ -6259,14 +6259,14 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/core-common": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
"dev": true,
"dependencies": {
"@storybook/core-events": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/core-events": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/types": "7.6.20",
"@types/find-cache-dir": "^3.2.1",
"@types/node": "^18.0.0",
"@types/node-fetch": "^2.6.4",
@ -6294,9 +6294,9 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/core-events": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
"dev": true,
"dependencies": {
"ts-dedent": "^2.0.0"
@ -6307,9 +6307,9 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/node-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
"dev": true,
"funding": {
"type": "opencollective",
@ -6317,12 +6317,12 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/types": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/channels": "7.6.20",
"@types/babel__core": "^7.0.0",
"@types/express": "^4.7.0",
"file-system-cache": "2.3.0"
@ -6438,23 +6438,23 @@
}
},
"node_modules/@storybook/cli": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.19.tgz",
"integrity": "sha512-7OVy7nPgkLfgivv6/dmvoyU6pKl9EzWFk+g9izyQHiM/jS8jOiEyn6akG8Ebj6k5pWslo5lgiXUSW+cEEZUnqQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.20.tgz",
"integrity": "sha512-ZlP+BJyqg7HlnXf7ypjG2CKMI/KVOn03jFIiClItE/jQfgR6kRFgtjRU7uajh427HHfjv9DRiur8nBzuO7vapA==",
"dev": true,
"dependencies": {
"@babel/core": "^7.23.2",
"@babel/preset-env": "^7.23.2",
"@babel/types": "^7.23.0",
"@ndelangen/get-tarball": "^3.0.7",
"@storybook/codemod": "7.6.19",
"@storybook/core-common": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/core-server": "7.6.19",
"@storybook/csf-tools": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/telemetry": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/codemod": "7.6.20",
"@storybook/core-common": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/core-server": "7.6.20",
"@storybook/csf-tools": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/telemetry": "7.6.20",
"@storybook/types": "7.6.20",
"@types/semver": "^7.3.4",
"@yarnpkg/fslib": "2.10.3",
"@yarnpkg/libzip": "2.3.0",
@ -6494,13 +6494,13 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/channels": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/global": "^5.0.0",
"qs": "^6.10.0",
"telejson": "^7.2.0",
@ -6512,9 +6512,9 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/client-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
"dev": true,
"dependencies": {
"@storybook/global": "^5.0.0"
@ -6525,14 +6525,14 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/core-common": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
"dev": true,
"dependencies": {
"@storybook/core-events": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/core-events": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/types": "7.6.20",
"@types/find-cache-dir": "^3.2.1",
"@types/node": "^18.0.0",
"@types/node-fetch": "^2.6.4",
@ -6560,9 +6560,9 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/core-events": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
"dev": true,
"dependencies": {
"ts-dedent": "^2.0.0"
@ -6573,9 +6573,9 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/csf-tools": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
"dev": true,
"dependencies": {
"@babel/generator": "^7.23.0",
@ -6583,7 +6583,7 @@
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"@storybook/csf": "^0.1.2",
"@storybook/types": "7.6.19",
"@storybook/types": "7.6.20",
"fs-extra": "^11.1.0",
"recast": "^0.23.1",
"ts-dedent": "^2.0.0"
@ -6594,9 +6594,9 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/node-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
"dev": true,
"funding": {
"type": "opencollective",
@ -6604,12 +6604,12 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/types": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/channels": "7.6.20",
"@types/babel__core": "^7.0.0",
"@types/express": "^4.7.0",
"file-system-cache": "2.3.0"
@ -6703,18 +6703,18 @@
}
},
"node_modules/@storybook/codemod": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.19.tgz",
"integrity": "sha512-bmHE0iEEgWZ65dXCmasd+GreChjPiWkXu2FEa0cJmNz/PqY12GsXGls4ke1TkNTj4gdSZnbtJxbclPZZnib2tQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.20.tgz",
"integrity": "sha512-8vmSsksO4XukNw0TmqylPmk7PxnfNfE21YsxFa7mnEBmEKQcZCQsNil4ZgWfG0IzdhTfhglAN4r++Ew0WE+PYA==",
"dev": true,
"dependencies": {
"@babel/core": "^7.23.2",
"@babel/preset-env": "^7.23.2",
"@babel/types": "^7.23.0",
"@storybook/csf": "^0.1.2",
"@storybook/csf-tools": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/csf-tools": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/types": "7.6.20",
"@types/cross-spawn": "^6.0.2",
"cross-spawn": "^7.0.3",
"globby": "^11.0.2",
@ -6729,13 +6729,13 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/channels": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/global": "^5.0.0",
"qs": "^6.10.0",
"telejson": "^7.2.0",
@ -6747,9 +6747,9 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/client-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
"dev": true,
"dependencies": {
"@storybook/global": "^5.0.0"
@ -6760,9 +6760,9 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/core-events": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
"dev": true,
"dependencies": {
"ts-dedent": "^2.0.0"
@ -6773,9 +6773,9 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/csf-tools": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
"dev": true,
"dependencies": {
"@babel/generator": "^7.23.0",
@ -6783,7 +6783,7 @@
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"@storybook/csf": "^0.1.2",
"@storybook/types": "7.6.19",
"@storybook/types": "7.6.20",
"fs-extra": "^11.1.0",
"recast": "^0.23.1",
"ts-dedent": "^2.0.0"
@ -6794,9 +6794,9 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/node-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
"dev": true,
"funding": {
"type": "opencollective",
@ -6804,12 +6804,12 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/types": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/channels": "7.6.20",
"@types/babel__core": "^7.0.0",
"@types/express": "^4.7.0",
"file-system-cache": "2.3.0"
@ -7063,26 +7063,26 @@
}
},
"node_modules/@storybook/core-server": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.19.tgz",
"integrity": "sha512-7mKL73Wv5R2bEl0kJ6QJ9bOu5YY53Idu24QgvTnUdNsQazp2yUONBNwHIrNDnNEXm8SfCi4Mc9o0mmNRMIoiRA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.20.tgz",
"integrity": "sha512-qC5BdbqqwMLTdCwMKZ1Hbc3+3AaxHYWLiJaXL9e8s8nJw89xV8c8l30QpbJOGvcDmsgY6UTtXYaJ96OsTr7MrA==",
"dev": true,
"dependencies": {
"@aw-web-design/x-default-browser": "1.4.126",
"@discoveryjs/json-ext": "^0.5.3",
"@storybook/builder-manager": "7.6.19",
"@storybook/channels": "7.6.19",
"@storybook/core-common": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/builder-manager": "7.6.20",
"@storybook/channels": "7.6.20",
"@storybook/core-common": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/csf": "^0.1.2",
"@storybook/csf-tools": "7.6.19",
"@storybook/csf-tools": "7.6.20",
"@storybook/docs-mdx": "^0.1.0",
"@storybook/global": "^5.0.0",
"@storybook/manager": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/preview-api": "7.6.19",
"@storybook/telemetry": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/manager": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/preview-api": "7.6.20",
"@storybook/telemetry": "7.6.20",
"@storybook/types": "7.6.20",
"@types/detect-port": "^1.3.0",
"@types/node": "^18.0.0",
"@types/pretty-hrtime": "^1.0.0",
@ -7095,7 +7095,6 @@
"express": "^4.17.3",
"fs-extra": "^11.1.0",
"globby": "^11.0.2",
"ip": "^2.0.1",
"lodash": "^4.17.21",
"open": "^8.4.0",
"pretty-hrtime": "^1.0.3",
@ -7116,13 +7115,13 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/channels": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/global": "^5.0.0",
"qs": "^6.10.0",
"telejson": "^7.2.0",
@ -7134,9 +7133,9 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/client-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
"dev": true,
"dependencies": {
"@storybook/global": "^5.0.0"
@ -7147,14 +7146,14 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/core-common": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
"dev": true,
"dependencies": {
"@storybook/core-events": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/core-events": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/types": "7.6.20",
"@types/find-cache-dir": "^3.2.1",
"@types/node": "^18.0.0",
"@types/node-fetch": "^2.6.4",
@ -7182,9 +7181,9 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/core-events": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
"dev": true,
"dependencies": {
"ts-dedent": "^2.0.0"
@ -7195,9 +7194,9 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/csf-tools": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
"dev": true,
"dependencies": {
"@babel/generator": "^7.23.0",
@ -7205,7 +7204,7 @@
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"@storybook/csf": "^0.1.2",
"@storybook/types": "7.6.19",
"@storybook/types": "7.6.20",
"fs-extra": "^11.1.0",
"recast": "^0.23.1",
"ts-dedent": "^2.0.0"
@ -7216,9 +7215,9 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/node-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
"dev": true,
"funding": {
"type": "opencollective",
@ -7226,17 +7225,17 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/preview-api": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.19.tgz",
"integrity": "sha512-04hdMSQucroJT4dBjQzRd7ZwH2hij8yx2nm5qd4HYGkd1ORkvlH6GOLph4XewNJl5Um3xfzFQzBhvkqvG0WaCQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.20.tgz",
"integrity": "sha512-3ic2m9LDZEPwZk02wIhNc3n3rNvbi7VDKn52hDXfAxnL5EYm7yDICAkaWcVaTfblru2zn0EDJt7ROpthscTW5w==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/channels": "7.6.20",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/csf": "^0.1.2",
"@storybook/global": "^5.0.0",
"@storybook/types": "7.6.19",
"@storybook/types": "7.6.20",
"@types/qs": "^6.9.5",
"dequal": "^2.0.2",
"lodash": "^4.17.21",
@ -7252,12 +7251,12 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/types": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/channels": "7.6.20",
"@types/babel__core": "^7.0.0",
"@types/express": "^4.7.0",
"file-system-cache": "2.3.0"
@ -7372,9 +7371,9 @@
"dev": true
},
"node_modules/@storybook/manager": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.19.tgz",
"integrity": "sha512-fZWQcf59x4P0iiBhrL74PZrqKJAPuk9sWjP8BIkGbf8wTZtUunbY5Sv4225fOL4NLJbuX9/RYLUPoxQ3nucGHA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.20.tgz",
"integrity": "sha512-0Cf6WN0t7yEG2DR29tN5j+i7H/TH5EfPppg9h9/KiQSoFHk+6KLoy2p5do94acFU+Ro4+zzxvdCGbcYGKuArpg==",
"dev": true,
"funding": {
"type": "opencollective",
@ -7810,14 +7809,14 @@
}
},
"node_modules/@storybook/telemetry": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.19.tgz",
"integrity": "sha512-rA5xum4I36M57iiD3uzmW0MOdpl0vEpHWBSAa5hK0a0ALPeY9TgAsQlI/0dSyNYJ/K7aczEEN6d4qm1NC4u10A==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.20.tgz",
"integrity": "sha512-dmAOCWmOscYN6aMbhCMmszQjoycg7tUPRVy2kTaWg6qX10wtMrvEtBV29W4eMvqdsoRj5kcvoNbzRdYcWBUOHQ==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-common": "7.6.19",
"@storybook/csf-tools": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-common": "7.6.20",
"@storybook/csf-tools": "7.6.20",
"chalk": "^4.1.0",
"detect-package-manager": "^2.0.1",
"fetch-retry": "^5.0.2",
@ -7830,13 +7829,13 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/channels": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/global": "^5.0.0",
"qs": "^6.10.0",
"telejson": "^7.2.0",
@ -7848,9 +7847,9 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/client-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
"dev": true,
"dependencies": {
"@storybook/global": "^5.0.0"
@ -7861,14 +7860,14 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/core-common": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
"dev": true,
"dependencies": {
"@storybook/core-events": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/core-events": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/types": "7.6.20",
"@types/find-cache-dir": "^3.2.1",
"@types/node": "^18.0.0",
"@types/node-fetch": "^2.6.4",
@ -7896,9 +7895,9 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/core-events": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
"dev": true,
"dependencies": {
"ts-dedent": "^2.0.0"
@ -7909,9 +7908,9 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/csf-tools": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
"dev": true,
"dependencies": {
"@babel/generator": "^7.23.0",
@ -7919,7 +7918,7 @@
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"@storybook/csf": "^0.1.2",
"@storybook/types": "7.6.19",
"@storybook/types": "7.6.20",
"fs-extra": "^11.1.0",
"recast": "^0.23.1",
"ts-dedent": "^2.0.0"
@ -7930,9 +7929,9 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/node-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
"dev": true,
"funding": {
"type": "opencollective",
@ -7940,12 +7939,12 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/types": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/channels": "7.6.20",
"@types/babel__core": "^7.0.0",
"@types/express": "^4.7.0",
"file-system-cache": "2.3.0"
@ -10040,12 +10039,13 @@
}
},
"node_modules/axios": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.28.0.tgz",
"integrity": "sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q==",
"dependencies": {
"follow-redirects": "^1.14.9",
"form-data": "^4.0.0"
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axios-auth-refresh": {
@ -10056,6 +10056,11 @@
"axios": ">= 0.18 < 0.19.0 || >= 0.19.1"
}
},
"node_modules/axios/node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@ -11451,6 +11456,12 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/confbox": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz",
"integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==",
"dev": true
},
"node_modules/confusing-browser-globals": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
@ -14389,9 +14400,9 @@
"dev": true
},
"node_modules/flow-parser": {
"version": "0.237.2",
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.237.2.tgz",
"integrity": "sha512-mvI/kdfr3l1waaPbThPA8dJa77nHXrfZIun+SWvFwSwDjmeByU7mGJGRmv1+7guU6ccyLV8e1lqZA1lD4iMGnQ==",
"version": "0.239.1",
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.239.1.tgz",
"integrity": "sha512-topOrETNxJ6T2gAnQiWqAlzGPj8uI2wtmNOlDIMNB+qyvGJZ6R++STbUOTAYmvPhOMz2gXnXPH0hOvURYmrBow==",
"dev": true,
"engines": {
"node": ">=0.4.0"
@ -18147,6 +18158,30 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"dev": true
},
"node_modules/mlly": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz",
"integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==",
"dev": true,
"dependencies": {
"acorn": "^8.11.3",
"pathe": "^1.1.2",
"pkg-types": "^1.1.1",
"ufo": "^1.5.3"
}
},
"node_modules/mlly/node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@ -18621,16 +18656,17 @@
}
},
"node_modules/nypm": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.8.tgz",
"integrity": "sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==",
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.9.tgz",
"integrity": "sha512-BI2SdqqTHg2d4wJh8P9A1W+bslg33vOE9IZDY6eR2QC+Pu1iNBVZUqczrd43rJb+fMzHU7ltAYKsEFY/kHMFcw==",
"dev": true,
"dependencies": {
"citty": "^0.1.6",
"consola": "^3.2.3",
"execa": "^8.0.1",
"pathe": "^1.1.2",
"ufo": "^1.4.0"
"pkg-types": "^1.1.1",
"ufo": "^1.5.3"
},
"bin": {
"nypm": "dist/cli.mjs"
@ -19360,9 +19396,9 @@
"devOptional": true
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@ -19405,6 +19441,17 @@
"node": ">=10"
}
},
"node_modules/pkg-types": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz",
"integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==",
"dev": true,
"dependencies": {
"confbox": "^0.1.7",
"mlly": "^1.7.1",
"pathe": "^1.1.2"
}
},
"node_modules/pnp-webpack-plugin": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz",
@ -19441,9 +19488,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.33",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
"version": "8.4.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
"funding": [
{
"type": "opencollective",
@ -19460,8 +19507,8 @@
],
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
"picocolors": "^1.0.1",
"source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
@ -20265,9 +20312,9 @@
}
},
"node_modules/puppeteer-core/node_modules/ws": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
"dev": true,
"dependencies": {
"async-limiter": "~1.0.0"
@ -22254,9 +22301,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"engines": {
"node": ">=0.10.0"
}
@ -22420,12 +22467,12 @@
"dev": true
},
"node_modules/storybook": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.19.tgz",
"integrity": "sha512-xWD1C4vD/4KMffCrBBrUpsLUO/9uNpm8BVW8+Vcb30gkQDfficZ0oziWkmLexpT53VSioa24iazGXMwBqllYjQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.20.tgz",
"integrity": "sha512-Wt04pPTO71pwmRmsgkyZhNo4Bvdb/1pBAMsIFb9nQLykEdzzpXjvingxFFvdOG4nIowzwgxD+CLlyRqVJqnATw==",
"dev": true,
"dependencies": {
"@storybook/cli": "7.6.19"
"@storybook/cli": "7.6.20"
},
"bin": {
"sb": "index.js",
@ -24661,9 +24708,9 @@
}
},
"node_modules/ws": {
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"dev": true,
"engines": {
"node": ">=10.0.0"

@ -55,7 +55,7 @@
"@ucast/mongo2js": "^1.3.4",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.27.2",
"axios": "^0.28.0",
"axios-auth-refresh": "^3.3.6",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
@ -144,10 +144,10 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^8.0.0",
"eslint-plugin-storybook": "^0.6.12",
"postcss": "^8.4.14",
"postcss": "^8.4.39",
"prettier": "^2.8.3",
"prettier-plugin-tailwindcss": "^0.2.2",
"storybook": "^7.5.2",
"storybook": "^7.6.20",
"storybook-dark-mode": "^3.0.0",
"tailwindcss": "3.2",
"typescript": "^4.9.3"

@ -39,4 +39,5 @@ export type SubscriptionPlan = {
trial_end: number | null;
has_used_trial: boolean;
caCrl: boolean;
instanceUserManagement: boolean;
};

@ -60,6 +60,7 @@ export type OrgUser = {
status: "invited" | "accepted" | "verified" | "completed";
deniedPermissions: any[];
roleId: string;
isActive: boolean;
};
export type TProjectMembership = {

@ -77,7 +77,7 @@ const features = [
"Pull secrets into your Kubernetes containers and automatically redeploy upon secret changes."
},
{
_id: 1,
id: 1,
name: "Infisical Agent",
link: "https://infisical.com/docs/infisical-agent/overview",
description: "Inject secrets into your apps without modifying any application logic."
@ -889,27 +889,27 @@ const OrganizationPage = withPermission(
<div className="mt-4 grid w-full grid-cols-3 gap-4">
{features.map((feature) => (
<div
key={feature._id}
className="flex h-44 w-full flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
key={feature.id}
className="relative flex h-full w-full flex-col gap-2 overflow-auto rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
>
<div className="mt-0 text-lg text-mineshaft-100">{feature.name}</div>
<div className="mb-4 mt-2 text-[15px] font-light text-mineshaft-300">
<div className="line-clamp overflwo-auto mb-4 mt-2 h-full text-[15px] font-light text-mineshaft-300">
{feature.description}
</div>
<div className="flex w-full items-center">
<div className="text-[15px] font-light text-mineshaft-300">
<div className="flex w-full flex-col items-start gap-2 xl:flex-row xl:items-center">
<p className="left-0 text-[15px] font-light text-mineshaft-300">
Setup time: 20 min
</div>
</p>
<a
target="_blank"
rel="noopener noreferrer"
className="group ml-auto w-max cursor-default rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200"
className="group ml-0 w-max cursor-default rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200 xl:ml-auto"
href={feature.link}
>
Learn more{" "}
<FontAwesomeIcon
icon={faArrowRight}
className="pl-1.5 pr-0.5 duration-200 group-hover:pl-2 group-hover:pr-0"
className="s pl-1.5 pr-0.5 duration-200 group-hover:pl-2 group-hover:pr-0"
/>
</a>
</div>

@ -22,7 +22,9 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
const schema = z
.object({
description: z.string(),
ttl: z.string(),
ttl: z.string().refine((val) => Number(val) <= 315360000, {
message: "TTL cannot be greater than 315360000"
}),
numUsesLimit: z.string()
})
.required();

@ -5,7 +5,7 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useOrganization,useWorkspace } from "@app/context";
import {
useAddIdentityToWorkspace,
useGetIdentityProjectMemberships,
@ -33,6 +33,7 @@ type Props = {
};
export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle }: Props) => {
const { currentOrg } = useOrganization();
const { workspaces } = useWorkspace();
const { mutateAsync: addIdentityToWorkspace } = useAddIdentityToWorkspace();
@ -58,7 +59,9 @@ export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle
wsWorkspaceIds.set(projectMembership.project.id, true);
});
return (workspaces || []).filter(({ id }) => !wsWorkspaceIds.has(id));
return (workspaces || []).filter(
({ id, orgId }) => !wsWorkspaceIds.has(id) && orgId === currentOrg?.id
);
}, [workspaces, projectMemberships]);
const onFormSubmit = async ({ projectId: workspaceId, role }: FormData) => {

@ -71,6 +71,7 @@ export const IdentityProjectRow = ({
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
<Tooltip content="Remove">
<IconButton
colorSchema="danger"
ariaLabel="copy icon"
variant="plain"
className="group relative"

@ -3,16 +3,10 @@ import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { withPermission } from "@app/hoc";
import {
OrgGroupsTab,
OrgIdentityTab,
OrgMembersTab,
OrgRoleTabSection
} from "./components";
import { OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./components";
enum TabSections {
Member = "members",
Groups = "groups",
Roles = "roles",
Identities = "identities"
}
@ -25,8 +19,7 @@ export const MembersPage = withPermission(
<p className="mr-4 mb-4 text-3xl font-semibold text-white">Organization Access Control</p>
<Tabs defaultValue={TabSections.Member}>
<TabList>
<Tab value={TabSections.Member}>People</Tab>
<Tab value={TabSections.Groups}>Groups</Tab>
<Tab value={TabSections.Member}>Users</Tab>
<Tab value={TabSections.Identities}>
<div className="flex items-center">
<p>Machine Identities</p>
@ -37,9 +30,6 @@ export const MembersPage = withPermission(
<TabPanel value={TabSections.Member}>
<OrgMembersTab />
</TabPanel>
<TabPanel value={TabSections.Groups}>
<OrgGroupsTab />
</TabPanel>
<TabPanel value={TabSections.Identities}>
<OrgIdentityTab />
</TabPanel>

@ -3,16 +3,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
UpgradePlanModal
} from "@app/components/v2";
import {
OrgPermissionActions,
OrgPermissionSubjects,
useSubscription
} from "@app/context";
import { Button, DeleteActionModal, UpgradePlanModal } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
import { useDeleteGroup } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
@ -21,100 +13,88 @@ import { OrgGroupModal } from "./OrgGroupModal";
import { OrgGroupsTable } from "./OrgGroupsTable";
export const OrgGroupsSection = () => {
const { subscription } = useSubscription();
const { mutateAsync: deleteMutateAsync } = useDeleteGroup();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"group",
"groupMembers",
"deleteGroup",
"upgradePlan"
] as const);
const handleAddGroupModal = () => {
if (!subscription?.groups) {
handlePopUpOpen("upgradePlan", {
description: "You can manage users more efficiently with groups if you upgrade your Infisical plan."
});
} else {
handlePopUpOpen("group");
}
const { subscription } = useSubscription();
const { mutateAsync: deleteMutateAsync } = useDeleteGroup();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"group",
"groupMembers",
"deleteGroup",
"upgradePlan"
] as const);
const handleAddGroupModal = () => {
if (!subscription?.groups) {
handlePopUpOpen("upgradePlan", {
description:
"You can manage users more efficiently with groups if you upgrade your Infisical plan."
});
} else {
handlePopUpOpen("group");
}
const onDeleteGroupSubmit = async ({
name,
};
const onDeleteGroupSubmit = async ({ name, slug }: { name: string; slug: string }) => {
try {
await deleteMutateAsync({
slug
}: {
name: string;
slug: string;
}) => {
try {
await deleteMutateAsync({
slug
});
createNotification({
text: `Successfully deleted the group named ${name}`,
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: `Failed to delete the group named ${name}`,
type: "error"
});
}
handlePopUpClose("deleteGroup");
});
createNotification({
text: `Successfully deleted the group named ${name}`,
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: `Failed to delete the group named ${name}`,
type: "error"
});
}
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Groups</p>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Groups}>
{(isAllowed) => (
<Button
colorSchema="primary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handleAddGroupModal()}
isDisabled={!isAllowed}
>
Create Group
</Button>
)}
</OrgPermissionCan>
</div>
<OrgGroupsTable
handlePopUpOpen={handlePopUpOpen}
/>
<OrgGroupModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}
handlePopUpToggle={handlePopUpToggle}
/>
<OrgGroupMembersModal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
/>
<DeleteActionModal
isOpen={popUp.deleteGroup.isOpen}
title={`Are you sure want to delete the group named ${
(popUp?.deleteGroup?.data as { name: string })?.name || ""
}?`}
onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onDeleteGroupSubmit(
(popUp?.deleteGroup?.data as { name: string; slug: string })
)
}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text={(popUp.upgradePlan?.data as { description: string })?.description}
/>
</div>
);
}
handlePopUpClose("deleteGroup");
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">User Groups</p>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Groups}>
{(isAllowed) => (
<Button
colorSchema="primary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handleAddGroupModal()}
isDisabled={!isAllowed}
>
Create Group
</Button>
)}
</OrgPermissionCan>
</div>
<OrgGroupsTable handlePopUpOpen={handlePopUpOpen} />
<OrgGroupModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}
handlePopUpToggle={handlePopUpToggle}
/>
<OrgGroupMembersModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deleteGroup.isOpen}
title={`Are you sure want to delete the group named ${
(popUp?.deleteGroup?.data as { name: string })?.name || ""
}?`}
onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onDeleteGroupSubmit(popUp?.deleteGroup?.data as { name: string; slug: string })
}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text={(popUp.upgradePlan?.data as { description: string })?.description}
/>
</div>
);
};

@ -1,12 +1,16 @@
import { useState } from "react";
import { faMagnifyingGlass, faPencil, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
import { faEllipsis,faMagnifyingGlass, faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
EmptyState,
IconButton,
Input,
Select,
SelectItem,
@ -17,218 +21,200 @@ import {
Td,
Th,
THead,
Tooltip,
Tr
} from "@app/components/v2";
import {
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization} from "@app/context";
import {
useGetOrganizationGroups,
useGetOrgRoles,
useUpdateGroup
} from "@app/hooks/api";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useGetOrganizationGroups, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<
["group", "deleteGroup", "groupMembers"]
>,
data?: {
groupId?: string;
name?: string;
slug?: string;
role?: string;
customRole?: {
name: string;
slug: string;
}
}
) => void;
};
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["group", "deleteGroup", "groupMembers"]>,
data?: {
groupId?: string;
name?: string;
slug?: string;
role?: string;
customRole?: {
name: string;
slug: string;
};
}
) => void;
};
export const OrgGroupsTable = ({
handlePopUpOpen
}: Props) => {
const [searchGroupsFilter, setSearchGroupsFilter] = useState("");
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { isLoading, data: groups } = useGetOrganizationGroups(orgId);
const { mutateAsync: updateMutateAsync } = useUpdateGroup();
const { data: roles } = useGetOrgRoles(orgId);
const handleChangeRole = async ({
export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
const [searchGroupsFilter, setSearchGroupsFilter] = useState("");
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { isLoading, data: groups } = useGetOrganizationGroups(orgId);
const { mutateAsync: updateMutateAsync } = useUpdateGroup();
const { data: roles } = useGetOrgRoles(orgId);
const handleChangeRole = async ({ currentSlug, role }: { currentSlug: string; role: string }) => {
try {
await updateMutateAsync({
currentSlug,
role
}: {
currentSlug: string;
role: string;
}) => {
try {
await updateMutateAsync({
currentSlug,
role
});
createNotification({
text: "Successfully updated group role",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update group role",
type: "error"
});
}
});
createNotification({
text: "Successfully updated group role",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update group role",
type: "error"
});
}
return (
<div>
<Input
value={searchGroupsFilter}
onChange={(e) => setSearchGroupsFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search groups..."
/>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>Slug</Th>
<Th>Role</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={4} innerKey="org-groups" />}
{!isLoading && groups?.map(({ id, name, slug, role, customRole }) => {
return (
<Tr className="h-10" key={`org-group-${id}`}>
<Td>{name}</Td>
<Td>{slug}</Td>
<Td>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Groups}
>
{(isAllowed) => {
return (
<Select
value={role === "custom" ? (customRole?.slug as string) : role}
isDisabled={!isAllowed}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
onValueChange={(selectedRole) =>
handleChangeRole({
currentSlug: slug,
role: selectedRole
})
}
>
{(roles || []).map(({ slug: roleSlug, name: roleName }) => (
<SelectItem value={roleSlug} key={`role-option-${roleSlug}`}>
{roleName}
</SelectItem>
))}
</Select>
);
}}
</OrgPermissionCan>
</Td>
<Td>
<div className="flex items-center justify-end">
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Groups}
>
{(isAllowed) => (
<Tooltip content="Manage group members">
<IconButton
onClick={() => {
handlePopUpOpen("groupMembers", {
slug
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faUsers} />
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Groups}
>
{(isAllowed) => (
<Tooltip content="Edit group">
<IconButton
onClick={async () => {
handlePopUpOpen("group", {
groupId: id,
name,
slug,
role,
customRole
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Delete}
a={OrgPermissionSubjects.Groups}
>
{(isAllowed) => (
<Tooltip content="Delete group">
<IconButton
onClick={() => {
handlePopUpOpen("deleteGroup", {
slug,
name
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
</div>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{groups?.length === 0 && (
<EmptyState title="No groups found" icon={faUsers} />
)}
</TableContainer>
</div>
);
}
};
return (
<div>
<Input
value={searchGroupsFilter}
onChange={(e) => setSearchGroupsFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search groups..."
/>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>Slug</Th>
<Th>Role</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={4} innerKey="org-groups" />}
{!isLoading &&
groups?.map(({ id, name, slug, role, customRole }) => {
return (
<Tr className="h-10" key={`org-group-${id}`}>
<Td>{name}</Td>
<Td>{slug}</Td>
<Td>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Groups}
>
{(isAllowed) => {
return (
<Select
value={role === "custom" ? (customRole?.slug as string) : role}
isDisabled={!isAllowed}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
onValueChange={(selectedRole) =>
handleChangeRole({
currentSlug: slug,
role: selectedRole
})
}
>
{(roles || []).map(({ slug: roleSlug, name: roleName }) => (
<SelectItem value={roleSlug} key={`role-option-${roleSlug}`}>
{roleName}
</SelectItem>
))}
</Select>
);
}}
</OrgPermissionCan>
</Td>
<Td>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<FontAwesomeIcon size="sm" icon={faEllipsis} />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("groupMembers", {
slug
});
}}
disabled={!isAllowed}
>
Manage Users
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("group", {
groupId: id,
name,
slug,
role,
customRole
});
}}
disabled={!isAllowed}
>
Edit Group
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Delete}
a={OrgPermissionSubjects.Groups}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
isAllowed
? "hover:!bg-red-500 hover:!text-white"
: "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("deleteGroup", {
slug,
name
});
}}
disabled={!isAllowed}
>
Delete Group
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{groups?.length === 0 && <EmptyState title="No groups found" icon={faUsers} />}
</TableContainer>
</div>
);
};

Some files were not shown because too many files have changed in this diff Show More