Compare commits

..

169 Commits

Author SHA1 Message Date
030d4fe152 misc: added handling of empty groups and default value 2024-07-01 21:10:27 +08:00
0b8f6878fe misc: added check for ldap group 2024-07-01 18:12:16 +08:00
0bb2b2887b updated handbook 2024-06-30 10:02:51 -07:00
eeb0111bbe updated handbook style 2024-06-30 02:03:59 -07:00
d12c538511 updated handbook 2024-06-30 02:01:40 -07:00
6f67346b2a Merge pull request #2042 from Infisical/daniel/fix-k8-managed-secret-crash
fix(k8-operator): crash on predefined managed secret
2024-06-28 17:01:37 -04:00
a93db44bbd Helm 2024-06-28 21:34:59 +02:00
1ddacfda62 Fix: Annotations map nil sometimes nil when pre-created by the user 2024-06-28 21:29:33 +02:00
351d0d0662 Merge pull request #2033 from Infisical/misc/added-secret-name-trim
misc: added secret name trimming
2024-06-29 01:14:23 +08:00
1859557f90 Merge pull request #2027 from akhilmhdh/feat/secret-manager-integration-auth
AWS Secret Manager assume role based integration
2024-06-27 23:32:25 -04:00
1b2a1f2339 Merge pull request #2019 from akhilmhdh/feat/read-replica
Postgres read replica support
2024-06-27 19:36:44 -04:00
15b4c397ab Merge pull request #2024 from Infisical/revert-2023-revert-1995-identity-based-pricing
Add support for Identity-Based Pricing"
2024-06-27 15:56:44 -07:00
fc27ad4575 Merge pull request #2037 from Infisical/create-pull-request/patch-1719509560
GH Action: rename new migration file timestamp
2024-06-27 23:07:16 +05:30
b7467a83ab chore: renamed new migration files to latest timestamp (gh-action) 2024-06-27 17:32:39 +00:00
3baf434230 Merge pull request #2034 from Infisical/misc/add-on-update-trigger-oidc
misc: add onUpdate trigger to oidc config
2024-06-27 23:02:14 +05:30
e28471a9f4 misc: add onUpdate trigger to oidc config 2024-06-27 19:55:01 +08:00
b2d6563994 misc: added secret name trimming 2024-06-27 19:41:00 +08:00
=
3537a5eb9b feat: switch to tabs instead of seperate pages for aws secret manager assume and access key 2024-06-27 13:06:04 +05:30
=
d5b17a8f24 feat: removed explicit check for aws access key credential allowing to pick it automatically 2024-06-27 13:05:30 +05:30
7cdc47cd3a Update build-staging-and-deploy-aws.yml 2024-06-26 18:55:13 -04:00
d666d60f9f Merge pull request #2030 from Infisical/doc/added-guide-for-configuring-certs
doc: added guide for configuring certs
2024-06-26 15:30:15 -04:00
491c4259ca small rephrase for gitlab cert docs 2024-06-26 15:29:44 -04:00
cff20eb621 doc: added guide for configuring certs 2024-06-27 03:00:15 +08:00
84d8879177 Merge pull request #2029 from Infisical/create-pull-request/patch-1719422383
GH Action: rename new migration file timestamp
2024-06-27 01:20:12 +08:00
aa4f2adbb6 Merge pull request #2028 from Infisical/create-pull-request/patch-1719422278
GH Action: rename new migration file timestamp
2024-06-27 01:19:50 +08:00
86ed3ef6d6 chore: renamed new migration files to latest timestamp (gh-action) 2024-06-26 17:19:42 +00:00
a5bb80adc4 Merge pull request #2020 from Infisical/feat/allow-audit-log-retention-to-be-configurable
feat: enabled customization of project audit logs retention period
2024-06-27 01:19:20 +08:00
0e87dd3996 chore: renamed new migration files to latest timestamp (gh-action) 2024-06-26 17:17:57 +00:00
e1801e9eb4 Merge pull request #2025 from Infisical/feat/added-support-for-configurable-ldap-user-identifier
misc: add support for configuring unique attribute property for ldap users
2024-06-27 01:16:51 +08:00
=
8eea82a1a0 docs: updated docs on usage of aws sm integration with assume role 2024-06-26 22:35:49 +05:30
=
694d0e3ed3 feat: updated ui for aws sm assume role integration 2024-06-26 22:35:12 +05:30
=
58f6c6b409 feat: updated integration api and queue to support aws secret manager assume role feature 2024-06-26 22:33:49 +05:30
f4a33caba6 misc: upgraded doc description of new field 2024-06-27 00:38:17 +08:00
e0a6f09b5e misc: removed max in schema for api layer 2024-06-26 23:17:31 +08:00
1e701687ae misc: adjusted other hook usage to incorporate new properties 2024-06-26 19:04:11 +08:00
15758b91f8 doc: updated ldap documentation 2024-06-26 18:54:34 +08:00
2d3a4a7559 feat: added support for configurable ldap user identifier 2024-06-26 18:36:28 +08:00
a1d01d5cbd misc: display retention settings only for self-hosted/dedicated 2024-06-26 12:44:56 +08:00
2e3aedc62b Merge pull request #2018 from Infisical/feat/add-initial-sync-behavior-azure
feat: added initial sync behavior for azure key integration
2024-06-26 11:43:54 +08:00
e0a5b1444a add step to install docker 2024-06-25 18:17:58 -04:00
1c2698f533 Revert "Revert "Add support for Identity-Based Pricing"" 2024-06-25 18:04:26 -04:00
b50833bded Merge pull request #2023 from Infisical/revert-1995-identity-based-pricing
Revert "Add support for Identity-Based Pricing"
2024-06-25 18:04:14 -04:00
e0c774c045 Revert "Add support for Identity-Based Pricing" 2024-06-25 18:03:07 -04:00
514df55d67 Merge pull request #1995 from Infisical/identity-based-pricing
Add support for Identity-Based Pricing
2024-06-25 14:50:18 -07:00
311b378f3b Merge pull request #1383 from Grraahaam/feat/cli-secret-plain-output
feat(cli): plain secret value output
2024-06-25 23:47:10 +02:00
b01b4323ca Fix merge conflicts 2024-06-25 14:46:58 -07:00
285a01af51 Merge pull request #2010 from Infisical/misc/add-documentation-for-bitbucket-cli-integration
doc: added bitbucket integration with cli
2024-06-25 16:33:07 -04:00
f7e658e62b rename bit bucket options 2024-06-25 16:31:56 -04:00
a8aef2934a Merge pull request #2021 from Infisical/feat/add-option-to-share-secrets-directly
feat: added option to share secret directly from main page
2024-06-25 15:55:04 -04:00
cc30476f79 Merge pull request #2022 from Infisical/misc/add-prompt-for-deleting-aws-sm-integration
misc: added proper prompt for aws secret manager integration deletion
2024-06-25 15:47:55 -04:00
=
5d59fe8810 fix: resolved rebase issue with knex.d.ts 2024-06-25 22:54:45 +05:30
=
90eed8d39b docs: updated replica information to the docs 2024-06-25 22:51:38 +05:30
=
f5974ce9ad feat: resolved some queries giving any[] on db instance modification for replication 2024-06-25 22:51:38 +05:30
=
c6b51af4b1 feat: removed knex-tables.d.ts 2024-06-25 22:51:37 +05:30
5139bf2385 misc: added delete prompt for aws secret manager integ deletion 2024-06-26 01:21:14 +08:00
=
c13c37fc77 feat: switched read db operations to replica nodes 2024-06-25 22:50:17 +05:30
=
259c01c110 feat: added read replica option in config and extended knex to choose 2024-06-25 22:49:28 +05:30
a016d0d33f Merge pull request #1999 from akhilmhdh/feat/ui-permission-check-broken
Terraform identity management apis
2024-06-25 22:46:53 +05:30
663be06d30 feat: added share secret to main page side nav 2024-06-26 00:22:47 +08:00
fa392382da feat: added option to share secret directly from main page 2024-06-25 23:41:17 +08:00
d34b2669c5 misc: finalized success message 2024-06-25 21:32:24 +08:00
11ea5990c9 feat: enabled customization of project audit logs retention period 2024-06-25 21:14:43 +08:00
9a66514178 Merge pull request #2007 from Infisical/feat/project-setting-for-rebuilding-index
feat: added project setting for rebuilding secret indices
2024-06-25 15:25:36 +08:00
d4f9faf24d feat: added initial sync behavior for azure key integration 2024-06-25 13:59:20 +08:00
a3c8d06845 Update overview.mdx 2024-06-24 20:25:53 -07:00
71b7be4057 Merge pull request #2017 from handotdev/patch-2
Update overview.mdx
2024-06-24 20:25:02 -07:00
5079a5889a Update overview.mdx 2024-06-24 17:37:35 -07:00
232b375f46 Merge pull request #2015 from Infisical/create-pull-request/patch-1719267521
GH Action: rename new migration file timestamp
2024-06-24 17:07:41 -07:00
d2acedf79e chore: renamed new migration files to latest timestamp (gh-action) 2024-06-24 22:18:39 +00:00
9d846319b0 Merge pull request #2014 from Infisical/cert-san
Add Certificate Support for Alt Names (SANs)
2024-06-24 15:18:17 -07:00
d69267a3ca remove console.log 2024-06-24 17:27:02 -04:00
051eee8701 Update altName example in docs 2024-06-24 14:14:51 -07:00
b5aa650899 Add cert support for alt names 2024-06-24 14:07:15 -07:00
376e185e2b Merge pull request #2006 from Infisical/daniel/expand-single-secret-ref
feat(api): Expand single secret references
2024-06-24 20:39:54 +02:00
a15a0a257c Update identity-router.ts 2024-06-24 20:38:11 +02:00
6facce220c update select default org login 2024-06-24 14:06:31 -04:00
620a423cee update org selection error message 2024-06-24 13:43:56 -04:00
361496c644 Merge pull request #2012 from Infisical/create-pull-request/patch-1719249628
GH Action: rename new migration file timestamp
2024-06-24 13:20:49 -04:00
e03f77d9cf chore: renamed new migration files to latest timestamp (gh-action) 2024-06-24 17:20:27 +00:00
60cb420242 Merge pull request #2000 from Infisical/daniel/default-org
Feat: Default organization slug for LDAP/SAML
2024-06-24 13:20:02 -04:00
1b8a77f507 Merge pull request #2002 from Infisical/patch-ldap
Patch LDAP undefined userId, email confirmation code sending
2024-06-24 13:19:48 -04:00
5a957514df Feat: Clear select option 2024-06-24 19:12:38 +02:00
a6865585f3 Fix: Failing to create admin config on first run 2024-06-24 19:11:58 +02:00
1aaca12781 Update super-admin-dal.ts 2024-06-24 19:11:58 +02:00
7ab5c02000 Requested changes 2024-06-24 19:11:58 +02:00
c735beea32 Fix: Requested changes 2024-06-24 19:11:58 +02:00
2d98560255 Updated "defaultOrgId" and "defaultOrgSlug" to "defaultAuthOrgId" and "defaultAuthOrgSlug" 2024-06-24 19:10:22 +02:00
91bdd7ea6a Fix: UI descriptions 2024-06-24 19:09:48 +02:00
b0f3476e4a Fix: Completely hide org slug input field when org slug is passed or default slug is provided 2024-06-24 19:09:03 +02:00
14751df9de Feat: Default SAML/LDAP organization slug 2024-06-24 19:09:03 +02:00
e1a4185f76 Hide org slug input when default slug is set 2024-06-24 19:08:19 +02:00
4905ad1f48 Feat: Default SAML/LDAP organization slug 2024-06-24 19:08:19 +02:00
56bc25025a Update Login.utils.tsx 2024-06-24 19:08:19 +02:00
45da563465 Convert navigate function to hook 2024-06-24 19:08:19 +02:00
1930d40be8 Feat: Default SAML/LDAP organization slug 2024-06-24 19:05:46 +02:00
30b8d59796 Feat: Default SAML/LDAP organization slug 2024-06-24 19:05:46 +02:00
aa6cca738e Update index.ts 2024-06-24 19:05:46 +02:00
04dee70a55 Type changes 2024-06-24 19:05:46 +02:00
dfb53dd333 Helper omit function 2024-06-24 19:05:20 +02:00
ab19e7df6d Feat: Default SAML/LDAP organization slug 2024-06-24 19:05:20 +02:00
f9a1accf84 Merge pull request #2011 from Infisical/create-pull-request/patch-1719245983
GH Action: rename new migration file timestamp
2024-06-25 00:24:08 +08:00
ca86f3d2b6 chore: renamed new migration files to latest timestamp (gh-action) 2024-06-24 16:19:41 +00:00
de466b4b86 Merge pull request #1989 from Infisical/feature/oidc
feat: oidc
2024-06-25 00:19:15 +08:00
745f1c4e12 misc: only display when user is admin 2024-06-24 23:24:07 +08:00
106dc261de Merge pull request #2008 from handotdev/patch-1
Update style.css in docs
2024-06-24 08:20:30 -07:00
548a0aed2a Merge pull request #2009 from Infisical/daniel/sdk-docs-typo
fix(docs): duplicate faq item
2024-06-24 07:28:52 -07:00
6029eaa9df misc: modified step title 2024-06-24 20:17:31 +08:00
8703314c0c doc: added bitbucket integration with cli 2024-06-24 20:11:53 +08:00
00617ea7e8 Update style.css 2024-06-24 00:56:39 -07:00
=
084fc7c99e feat: resolved gcp auth revoke error 2024-06-23 01:25:27 +05:30
=
b6cc17d62a feat: updated var names and permission, rate limit changes based on comments 2024-06-23 01:21:26 +05:30
bd0d0bd333 feat: added project setting for rebuilding secret indices 2024-06-22 22:42:36 +08:00
c426ba517a Feat: Expand single secret references 2024-06-21 23:12:38 +02:00
973403c7f9 Merge branch 'feature/oidc' of https://github.com/Infisical/infisical into feature/oidc 2024-06-21 22:23:23 +08:00
52fcf53d0e misc: moved authenticate to preValidation 2024-06-21 22:22:34 +08:00
91634fbe76 Patch LDAP 2024-06-20 17:49:09 -07:00
f31340cf53 Minor adjustments to oidc docs 2024-06-20 16:34:21 -07:00
193d6dad54 misc: removed read sso from org member 2024-06-21 00:39:58 +08:00
0f36fc46b3 docs: added docs for general oidc configuration 2024-06-21 00:37:37 +08:00
=
4072a40fe9 feat: completed all revoke for other identity auth 2024-06-20 21:18:45 +05:30
=
0dc132dda3 feat: added universal auth endpoints for revoke and client secret endpoint to fetch details 2024-06-20 21:18:45 +05:30
=
605ccb13e9 feat: added endpoints and docs for identity get by id and list operation 2024-06-20 21:18:45 +05:30
4a1a399fd8 docs: added documentation for auth0 oidc configuration 2024-06-20 22:56:53 +08:00
d19e2f64f0 misc: added oidc to user alias type 2024-06-20 21:14:47 +08:00
1e0f54d9a4 doc: added mentions of oidc 2024-06-20 21:14:09 +08:00
8d55c2802e misc: added redirect after user creation 2024-06-20 20:55:26 +08:00
e9639df8ce docs: added keycloak-oidc documentation 2024-06-20 20:25:57 +08:00
e0f5ecbe7b misc: added oidc to text label 2024-06-20 15:40:40 +08:00
2160c66e20 doc(cli): improve --plain example + add --silent notice 2024-06-20 09:15:19 +02:00
1c5c7c75c4 Merge remote-tracking branch 'origin/main' into feat/cli-secret-plain-output 2024-06-20 08:50:11 +02:00
3e230555fb misc: added oifc checks to signup 2024-06-20 13:59:50 +08:00
24c75c6325 Merge remote-tracking branch 'origin' into identity-based-pricing 2024-06-19 14:18:13 -07:00
0a22a2a9ef Readjustments 2024-06-19 14:16:32 -07:00
d0f1cad98c Add support for identity-based pricing 2024-06-19 13:59:47 -07:00
ad92565783 misc: grammar update 2024-06-20 03:30:03 +08:00
6c98c96a15 misc: added comment for samesite lax 2024-06-20 03:29:20 +08:00
f0a70d8769 misc: added samesite lax 2024-06-20 03:19:10 +08:00
d64e2fa243 misc: added client id and secret focus toggle 2024-06-20 01:37:52 +08:00
afee158b95 Start adding identity based pricing logic 2024-06-19 10:31:58 -07:00
ecca6f4db5 Merge remote-tracking branch 'origin/main' into feature/oidc 2024-06-20 01:21:46 +08:00
b198f97930 misc: added oidc create validation in route 2024-06-20 00:28:14 +08:00
63a9e46936 misc: removed unnecessary zod assertions 2024-06-20 00:20:00 +08:00
7c067551a4 misc: added frontend validation for oidc form 2024-06-20 00:15:18 +08:00
1193ddbed1 misc: added rate limit for oidc login endpoint 2024-06-19 23:02:49 +08:00
6457c34712 misc: addressed eslint issue regarding configurationType 2024-06-19 22:41:33 +08:00
6a83b58de4 misc: added support for dynamic discovery of OIDC configuration 2024-06-19 22:33:50 +08:00
0100ddfb99 misc: addressed review comments 2024-06-19 19:35:02 +08:00
2bc6db1c47 misc: readded cookie nginx config for dev 2024-06-19 14:19:27 +08:00
92f2f16656 misc: added option for trusting OIDC emails by default 2024-06-19 13:46:17 +08:00
18e69578f0 feat: added support for limiting email domains 2024-06-19 01:29:26 +08:00
0685a5ea8b Merge remote-tracking branch 'origin/main' into feature/oidc 2024-06-18 23:54:06 +08:00
bdc7c018eb misc: added comment regarding session and redis usage 2024-06-18 20:36:09 +08:00
bcd65333c0 misc: added handling of inactive and undefined oidc config 2024-06-18 19:17:54 +08:00
371b96a13a misc: removed cookie path proxy for dev envs 2024-06-18 15:36:49 +08:00
c5c00b520c misc: added session regenerate for fresh state 2024-06-18 15:35:18 +08:00
8de4443be1 feat: added support for login via cli 2024-06-18 15:26:08 +08:00
96ad3b0264 misc: used redis for oic session managemen 2024-06-18 14:20:04 +08:00
df51d05c46 feat: integrated oidc with sso login 2024-06-18 02:10:08 +08:00
4f2f7b2f70 misc: moved oidc endpoints to /sso 2024-06-18 01:21:42 +08:00
d79ffbe37e misc: added license checks for oidc sso 2024-06-18 01:11:57 +08:00
2c237ee277 feat: moved oidc to ee directory 2024-06-18 00:53:56 +08:00
56cc248425 feature: finalized oidc core service methods 2024-06-17 23:28:27 +08:00
61fcb2b605 feat: finished oidc form functions 2024-06-17 22:37:23 +08:00
66e5edcfc0 feat: oidc poc 2024-06-17 20:32:47 +08:00
f9a9599659 doc(cli): improve --plain example 2024-05-24 17:29:44 +02:00
637b0b955f fix(cli): add backward compatibility for --raw-value 2024-05-24 17:28:52 +02:00
092665737f Merge remote-tracking branch 'origin/main' into feat/cli-secret-plain-output 2024-05-24 17:13:57 +02:00
f83c2215a5 doc(cli): --plain 2024-02-08 17:44:55 +01:00
0f41590d6a feat(cli): --plain, --expand, --include-imports in 'secrets get' subcommand 2024-02-08 17:32:31 +01:00
248 changed files with 7117 additions and 803 deletions

View File

@ -56,7 +56,7 @@ jobs:
# Learn more about [Twingate Services](https://docs.twingate.com/docs/services) # Learn more about [Twingate Services](https://docs.twingate.com/docs/services)
# #
# Required # Required
service-key: ${{ secrets.TWINGATE_GAMMA_SERVICE_KEY }} service-key: ${{ secrets.TWINGATE_SERVICE_KEY }}
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup Node.js environment - name: Setup Node.js environment

View File

@ -5,3 +5,4 @@ frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/M
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:292 frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:292
docs/self-hosting/configuration/envars.mdx:generic-api-key:106 docs/self-hosting/configuration/envars.mdx:generic-api-key:106
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:451 frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:451
docs/mint.json:generic-api-key:651

View File

@ -3,7 +3,6 @@ import "ts-node/register";
import dotenv from "dotenv"; import dotenv from "dotenv";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import knex from "knex";
import path from "path"; import path from "path";
import { seedData1 } from "@app/db/seed-data"; import { seedData1 } from "@app/db/seed-data";
@ -15,6 +14,7 @@ import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { mockQueue } from "./mocks/queue"; import { mockQueue } from "./mocks/queue";
import { mockSmtpServer } from "./mocks/smtp"; import { mockSmtpServer } from "./mocks/smtp";
import { mockKeyStore } from "./mocks/keystore"; import { mockKeyStore } from "./mocks/keystore";
import { initDbConnection } from "@app/db";
dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true }); dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true });
export default { export default {
@ -23,23 +23,21 @@ export default {
async setup() { async setup() {
const logger = await initLogger(); const logger = await initLogger();
const cfg = initEnvConfig(logger); const cfg = initEnvConfig(logger);
const db = knex({ const db = initDbConnection({
client: "pg", dbConnectionUri: cfg.DB_CONNECTION_URI,
connection: cfg.DB_CONNECTION_URI, dbRootCert: cfg.DB_ROOT_CERT
migrations: {
directory: path.join(__dirname, "../src/db/migrations"),
extension: "ts",
tableName: "infisical_migrations"
},
seeds: {
directory: path.join(__dirname, "../src/db/seeds"),
extension: "ts"
}
}); });
try { try {
await db.migrate.latest(); await db.migrate.latest({
await db.seed.run(); directory: path.join(__dirname, "../src/db/migrations"),
extension: "ts",
tableName: "infisical_migrations"
});
await db.seed.run({
directory: path.join(__dirname, "../src/db/seeds"),
extension: "ts"
});
const smtp = mockSmtpServer(); const smtp = mockSmtpServer();
const queue = mockQueue(); const queue = mockQueue();
const keyStore = mockKeyStore(); const keyStore = mockKeyStore();
@ -74,7 +72,14 @@ export default {
// @ts-expect-error type // @ts-expect-error type
delete globalThis.jwtToken; delete globalThis.jwtToken;
// called after all tests with this env have been run // called after all tests with this env have been run
await db.migrate.rollback({}, true); await db.migrate.rollback(
{
directory: path.join(__dirname, "../src/db/migrations"),
extension: "ts",
tableName: "infisical_migrations"
},
true
);
await db.destroy(); await db.destroy();
} }
}; };

1189
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -72,6 +72,7 @@
"dependencies": { "dependencies": {
"@aws-sdk/client-iam": "^3.525.0", "@aws-sdk/client-iam": "^3.525.0",
"@aws-sdk/client-secrets-manager": "^3.504.0", "@aws-sdk/client-secrets-manager": "^3.504.0",
"@aws-sdk/client-sts": "^3.600.0",
"@casl/ability": "^6.5.0", "@casl/ability": "^6.5.0",
"@fastify/cookie": "^9.3.1", "@fastify/cookie": "^9.3.1",
"@fastify/cors": "^8.5.0", "@fastify/cors": "^8.5.0",
@ -99,6 +100,7 @@
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"bullmq": "^5.4.2", "bullmq": "^5.4.2",
"cassandra-driver": "^4.7.2", "cassandra-driver": "^4.7.2",
"connect-redis": "^7.1.1",
"cron": "^3.1.7", "cron": "^3.1.7",
"dotenv": "^16.4.1", "dotenv": "^16.4.1",
"fastify": "^4.26.0", "fastify": "^4.26.0",
@ -118,6 +120,7 @@
"mysql2": "^3.9.8", "mysql2": "^3.9.8",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"nodemailer": "^6.9.9", "nodemailer": "^6.9.9",
"openid-client": "^5.6.5",
"ora": "^7.0.1", "ora": "^7.0.1",
"oracledb": "^6.4.0", "oracledb": "^6.4.0",
"passport-github": "^1.1.0", "passport-github": "^1.1.0",

View File

@ -13,6 +13,7 @@ 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 { 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"; import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TOidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service"; import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
import { TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service"; import { TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service";
@ -102,6 +103,7 @@ declare module "fastify" {
permission: TPermissionServiceFactory; permission: TPermissionServiceFactory;
org: TOrgServiceFactory; org: TOrgServiceFactory;
orgRole: TOrgRoleServiceFactory; orgRole: TOrgRoleServiceFactory;
oidc: TOidcConfigServiceFactory;
superAdmin: TSuperAdminServiceFactory; superAdmin: TSuperAdminServiceFactory;
user: TUserServiceFactory; user: TUserServiceFactory;
group: TGroupServiceFactory; group: TGroupServiceFactory;

View File

@ -1,4 +1,4 @@
import { Knex } from "knex"; import { Knex as KnexOriginal } from "knex";
import { import {
TableName, TableName,
@ -134,6 +134,9 @@ import {
TLdapGroupMaps, TLdapGroupMaps,
TLdapGroupMapsInsert, TLdapGroupMapsInsert,
TLdapGroupMapsUpdate, TLdapGroupMapsUpdate,
TOidcConfigs,
TOidcConfigsInsert,
TOidcConfigsUpdate,
TOrganizations, TOrganizations,
TOrganizationsInsert, TOrganizationsInsert,
TOrganizationsUpdate, TOrganizationsUpdate,
@ -277,317 +280,371 @@ import {
TWebhooksUpdate TWebhooksUpdate
} from "@app/db/schemas"; } from "@app/db/schemas";
declare module "knex" {
namespace Knex {
interface QueryInterface {
primaryNode(): KnexOriginal;
replicaNode(): KnexOriginal;
}
}
}
declare module "knex/types/tables" { declare module "knex/types/tables" {
interface Tables { interface Tables {
[TableName.Users]: Knex.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>; [TableName.Users]: KnexOriginal.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
[TableName.Groups]: Knex.CompositeTableType<TGroups, TGroupsInsert, TGroupsUpdate>; [TableName.Groups]: KnexOriginal.CompositeTableType<TGroups, TGroupsInsert, TGroupsUpdate>;
[TableName.CertificateAuthority]: Knex.CompositeTableType< [TableName.CertificateAuthority]: KnexOriginal.CompositeTableType<
TCertificateAuthorities, TCertificateAuthorities,
TCertificateAuthoritiesInsert, TCertificateAuthoritiesInsert,
TCertificateAuthoritiesUpdate TCertificateAuthoritiesUpdate
>; >;
[TableName.CertificateAuthorityCert]: Knex.CompositeTableType< [TableName.CertificateAuthorityCert]: KnexOriginal.CompositeTableType<
TCertificateAuthorityCerts, TCertificateAuthorityCerts,
TCertificateAuthorityCertsInsert, TCertificateAuthorityCertsInsert,
TCertificateAuthorityCertsUpdate TCertificateAuthorityCertsUpdate
>; >;
[TableName.CertificateAuthoritySecret]: Knex.CompositeTableType< [TableName.CertificateAuthoritySecret]: KnexOriginal.CompositeTableType<
TCertificateAuthoritySecret, TCertificateAuthoritySecret,
TCertificateAuthoritySecretInsert, TCertificateAuthoritySecretInsert,
TCertificateAuthoritySecretUpdate TCertificateAuthoritySecretUpdate
>; >;
[TableName.CertificateAuthorityCrl]: Knex.CompositeTableType< [TableName.CertificateAuthorityCrl]: KnexOriginal.CompositeTableType<
TCertificateAuthorityCrl, TCertificateAuthorityCrl,
TCertificateAuthorityCrlInsert, TCertificateAuthorityCrlInsert,
TCertificateAuthorityCrlUpdate TCertificateAuthorityCrlUpdate
>; >;
[TableName.Certificate]: Knex.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>; [TableName.Certificate]: KnexOriginal.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
[TableName.CertificateBody]: Knex.CompositeTableType< [TableName.CertificateBody]: KnexOriginal.CompositeTableType<
TCertificateBodies, TCertificateBodies,
TCertificateBodiesInsert, TCertificateBodiesInsert,
TCertificateBodiesUpdate TCertificateBodiesUpdate
>; >;
[TableName.CertificateSecret]: Knex.CompositeTableType< [TableName.CertificateSecret]: KnexOriginal.CompositeTableType<
TCertificateSecrets, TCertificateSecrets,
TCertificateSecretsInsert, TCertificateSecretsInsert,
TCertificateSecretsUpdate TCertificateSecretsUpdate
>; >;
[TableName.UserGroupMembership]: Knex.CompositeTableType< [TableName.UserGroupMembership]: KnexOriginal.CompositeTableType<
TUserGroupMembership, TUserGroupMembership,
TUserGroupMembershipInsert, TUserGroupMembershipInsert,
TUserGroupMembershipUpdate TUserGroupMembershipUpdate
>; >;
[TableName.GroupProjectMembership]: Knex.CompositeTableType< [TableName.GroupProjectMembership]: KnexOriginal.CompositeTableType<
TGroupProjectMemberships, TGroupProjectMemberships,
TGroupProjectMembershipsInsert, TGroupProjectMembershipsInsert,
TGroupProjectMembershipsUpdate TGroupProjectMembershipsUpdate
>; >;
[TableName.GroupProjectMembershipRole]: Knex.CompositeTableType< [TableName.GroupProjectMembershipRole]: KnexOriginal.CompositeTableType<
TGroupProjectMembershipRoles, TGroupProjectMembershipRoles,
TGroupProjectMembershipRolesInsert, TGroupProjectMembershipRolesInsert,
TGroupProjectMembershipRolesUpdate TGroupProjectMembershipRolesUpdate
>; >;
[TableName.UserAliases]: Knex.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>; [TableName.UserAliases]: KnexOriginal.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>;
[TableName.UserEncryptionKey]: Knex.CompositeTableType< [TableName.UserEncryptionKey]: KnexOriginal.CompositeTableType<
TUserEncryptionKeys, TUserEncryptionKeys,
TUserEncryptionKeysInsert, TUserEncryptionKeysInsert,
TUserEncryptionKeysUpdate TUserEncryptionKeysUpdate
>; >;
[TableName.AuthTokens]: Knex.CompositeTableType<TAuthTokens, TAuthTokensInsert, TAuthTokensUpdate>; [TableName.AuthTokens]: KnexOriginal.CompositeTableType<TAuthTokens, TAuthTokensInsert, TAuthTokensUpdate>;
[TableName.AuthTokenSession]: Knex.CompositeTableType< [TableName.AuthTokenSession]: KnexOriginal.CompositeTableType<
TAuthTokenSessions, TAuthTokenSessions,
TAuthTokenSessionsInsert, TAuthTokenSessionsInsert,
TAuthTokenSessionsUpdate TAuthTokenSessionsUpdate
>; >;
[TableName.BackupPrivateKey]: Knex.CompositeTableType< [TableName.BackupPrivateKey]: KnexOriginal.CompositeTableType<
TBackupPrivateKey, TBackupPrivateKey,
TBackupPrivateKeyInsert, TBackupPrivateKeyInsert,
TBackupPrivateKeyUpdate TBackupPrivateKeyUpdate
>; >;
[TableName.Organization]: Knex.CompositeTableType<TOrganizations, TOrganizationsInsert, TOrganizationsUpdate>; [TableName.Organization]: KnexOriginal.CompositeTableType<
[TableName.OrgMembership]: Knex.CompositeTableType<TOrgMemberships, TOrgMembershipsInsert, TOrgMembershipsUpdate>; TOrganizations,
[TableName.OrgRoles]: Knex.CompositeTableType<TOrgRoles, TOrgRolesInsert, TOrgRolesUpdate>; TOrganizationsInsert,
[TableName.IncidentContact]: Knex.CompositeTableType< TOrganizationsUpdate
>;
[TableName.OrgMembership]: KnexOriginal.CompositeTableType<
TOrgMemberships,
TOrgMembershipsInsert,
TOrgMembershipsUpdate
>;
[TableName.OrgRoles]: KnexOriginal.CompositeTableType<TOrgRoles, TOrgRolesInsert, TOrgRolesUpdate>;
[TableName.IncidentContact]: KnexOriginal.CompositeTableType<
TIncidentContacts, TIncidentContacts,
TIncidentContactsInsert, TIncidentContactsInsert,
TIncidentContactsUpdate TIncidentContactsUpdate
>; >;
[TableName.UserAction]: Knex.CompositeTableType<TUserActions, TUserActionsInsert, TUserActionsUpdate>; [TableName.UserAction]: KnexOriginal.CompositeTableType<TUserActions, TUserActionsInsert, TUserActionsUpdate>;
[TableName.SuperAdmin]: Knex.CompositeTableType<TSuperAdmin, TSuperAdminInsert, TSuperAdminUpdate>; [TableName.SuperAdmin]: KnexOriginal.CompositeTableType<TSuperAdmin, TSuperAdminInsert, TSuperAdminUpdate>;
[TableName.ApiKey]: Knex.CompositeTableType<TApiKeys, TApiKeysInsert, TApiKeysUpdate>; [TableName.ApiKey]: KnexOriginal.CompositeTableType<TApiKeys, TApiKeysInsert, TApiKeysUpdate>;
[TableName.Project]: Knex.CompositeTableType<TProjects, TProjectsInsert, TProjectsUpdate>; [TableName.Project]: KnexOriginal.CompositeTableType<TProjects, TProjectsInsert, TProjectsUpdate>;
[TableName.ProjectMembership]: Knex.CompositeTableType< [TableName.ProjectMembership]: KnexOriginal.CompositeTableType<
TProjectMemberships, TProjectMemberships,
TProjectMembershipsInsert, TProjectMembershipsInsert,
TProjectMembershipsUpdate TProjectMembershipsUpdate
>; >;
[TableName.Environment]: Knex.CompositeTableType< [TableName.Environment]: KnexOriginal.CompositeTableType<
TProjectEnvironments, TProjectEnvironments,
TProjectEnvironmentsInsert, TProjectEnvironmentsInsert,
TProjectEnvironmentsUpdate TProjectEnvironmentsUpdate
>; >;
[TableName.ProjectBot]: Knex.CompositeTableType<TProjectBots, TProjectBotsInsert, TProjectBotsUpdate>; [TableName.ProjectBot]: KnexOriginal.CompositeTableType<TProjectBots, TProjectBotsInsert, TProjectBotsUpdate>;
[TableName.ProjectUserMembershipRole]: Knex.CompositeTableType< [TableName.ProjectUserMembershipRole]: KnexOriginal.CompositeTableType<
TProjectUserMembershipRoles, TProjectUserMembershipRoles,
TProjectUserMembershipRolesInsert, TProjectUserMembershipRolesInsert,
TProjectUserMembershipRolesUpdate TProjectUserMembershipRolesUpdate
>; >;
[TableName.ProjectRoles]: Knex.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>; [TableName.ProjectRoles]: KnexOriginal.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>;
[TableName.ProjectUserAdditionalPrivilege]: Knex.CompositeTableType< [TableName.ProjectUserAdditionalPrivilege]: KnexOriginal.CompositeTableType<
TProjectUserAdditionalPrivilege, TProjectUserAdditionalPrivilege,
TProjectUserAdditionalPrivilegeInsert, TProjectUserAdditionalPrivilegeInsert,
TProjectUserAdditionalPrivilegeUpdate TProjectUserAdditionalPrivilegeUpdate
>; >;
[TableName.ProjectKeys]: Knex.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>; [TableName.ProjectKeys]: KnexOriginal.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>;
[TableName.Secret]: Knex.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>; [TableName.Secret]: KnexOriginal.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>;
[TableName.SecretReference]: Knex.CompositeTableType< [TableName.SecretReference]: KnexOriginal.CompositeTableType<
TSecretReferences, TSecretReferences,
TSecretReferencesInsert, TSecretReferencesInsert,
TSecretReferencesUpdate TSecretReferencesUpdate
>; >;
[TableName.SecretBlindIndex]: Knex.CompositeTableType< [TableName.SecretBlindIndex]: KnexOriginal.CompositeTableType<
TSecretBlindIndexes, TSecretBlindIndexes,
TSecretBlindIndexesInsert, TSecretBlindIndexesInsert,
TSecretBlindIndexesUpdate TSecretBlindIndexesUpdate
>; >;
[TableName.SecretVersion]: Knex.CompositeTableType<TSecretVersions, TSecretVersionsInsert, TSecretVersionsUpdate>; [TableName.SecretVersion]: KnexOriginal.CompositeTableType<
[TableName.SecretFolder]: Knex.CompositeTableType<TSecretFolders, TSecretFoldersInsert, TSecretFoldersUpdate>; TSecretVersions,
[TableName.SecretFolderVersion]: Knex.CompositeTableType< TSecretVersionsInsert,
TSecretVersionsUpdate
>;
[TableName.SecretFolder]: KnexOriginal.CompositeTableType<
TSecretFolders,
TSecretFoldersInsert,
TSecretFoldersUpdate
>;
[TableName.SecretFolderVersion]: KnexOriginal.CompositeTableType<
TSecretFolderVersions, TSecretFolderVersions,
TSecretFolderVersionsInsert, TSecretFolderVersionsInsert,
TSecretFolderVersionsUpdate TSecretFolderVersionsUpdate
>; >;
[TableName.SecretSharing]: Knex.CompositeTableType<TSecretSharing, TSecretSharingInsert, TSecretSharingUpdate>; [TableName.SecretSharing]: KnexOriginal.CompositeTableType<
[TableName.RateLimit]: Knex.CompositeTableType<TRateLimit, TRateLimitInsert, TRateLimitUpdate>; TSecretSharing,
[TableName.SecretTag]: Knex.CompositeTableType<TSecretTags, TSecretTagsInsert, TSecretTagsUpdate>; TSecretSharingInsert,
[TableName.SecretImport]: Knex.CompositeTableType<TSecretImports, TSecretImportsInsert, TSecretImportsUpdate>; TSecretSharingUpdate
[TableName.Integration]: Knex.CompositeTableType<TIntegrations, TIntegrationsInsert, TIntegrationsUpdate>; >;
[TableName.Webhook]: Knex.CompositeTableType<TWebhooks, TWebhooksInsert, TWebhooksUpdate>; [TableName.RateLimit]: KnexOriginal.CompositeTableType<TRateLimit, TRateLimitInsert, TRateLimitUpdate>;
[TableName.ServiceToken]: Knex.CompositeTableType<TServiceTokens, TServiceTokensInsert, TServiceTokensUpdate>; [TableName.SecretTag]: KnexOriginal.CompositeTableType<TSecretTags, TSecretTagsInsert, TSecretTagsUpdate>;
[TableName.IntegrationAuth]: Knex.CompositeTableType< [TableName.SecretImport]: KnexOriginal.CompositeTableType<
TSecretImports,
TSecretImportsInsert,
TSecretImportsUpdate
>;
[TableName.Integration]: KnexOriginal.CompositeTableType<TIntegrations, TIntegrationsInsert, TIntegrationsUpdate>;
[TableName.Webhook]: KnexOriginal.CompositeTableType<TWebhooks, TWebhooksInsert, TWebhooksUpdate>;
[TableName.ServiceToken]: KnexOriginal.CompositeTableType<
TServiceTokens,
TServiceTokensInsert,
TServiceTokensUpdate
>;
[TableName.IntegrationAuth]: KnexOriginal.CompositeTableType<
TIntegrationAuths, TIntegrationAuths,
TIntegrationAuthsInsert, TIntegrationAuthsInsert,
TIntegrationAuthsUpdate TIntegrationAuthsUpdate
>; >;
[TableName.Identity]: Knex.CompositeTableType<TIdentities, TIdentitiesInsert, TIdentitiesUpdate>; [TableName.Identity]: KnexOriginal.CompositeTableType<TIdentities, TIdentitiesInsert, TIdentitiesUpdate>;
[TableName.IdentityUniversalAuth]: Knex.CompositeTableType< [TableName.IdentityUniversalAuth]: KnexOriginal.CompositeTableType<
TIdentityUniversalAuths, TIdentityUniversalAuths,
TIdentityUniversalAuthsInsert, TIdentityUniversalAuthsInsert,
TIdentityUniversalAuthsUpdate TIdentityUniversalAuthsUpdate
>; >;
[TableName.IdentityKubernetesAuth]: Knex.CompositeTableType< [TableName.IdentityKubernetesAuth]: KnexOriginal.CompositeTableType<
TIdentityKubernetesAuths, TIdentityKubernetesAuths,
TIdentityKubernetesAuthsInsert, TIdentityKubernetesAuthsInsert,
TIdentityKubernetesAuthsUpdate TIdentityKubernetesAuthsUpdate
>; >;
[TableName.IdentityGcpAuth]: Knex.CompositeTableType< [TableName.IdentityGcpAuth]: KnexOriginal.CompositeTableType<
TIdentityGcpAuths, TIdentityGcpAuths,
TIdentityGcpAuthsInsert, TIdentityGcpAuthsInsert,
TIdentityGcpAuthsUpdate TIdentityGcpAuthsUpdate
>; >;
[TableName.IdentityAwsAuth]: Knex.CompositeTableType< [TableName.IdentityAwsAuth]: KnexOriginal.CompositeTableType<
TIdentityAwsAuths, TIdentityAwsAuths,
TIdentityAwsAuthsInsert, TIdentityAwsAuthsInsert,
TIdentityAwsAuthsUpdate TIdentityAwsAuthsUpdate
>; >;
[TableName.IdentityAzureAuth]: Knex.CompositeTableType< [TableName.IdentityAzureAuth]: KnexOriginal.CompositeTableType<
TIdentityAzureAuths, TIdentityAzureAuths,
TIdentityAzureAuthsInsert, TIdentityAzureAuthsInsert,
TIdentityAzureAuthsUpdate TIdentityAzureAuthsUpdate
>; >;
[TableName.IdentityUaClientSecret]: Knex.CompositeTableType< [TableName.IdentityUaClientSecret]: KnexOriginal.CompositeTableType<
TIdentityUaClientSecrets, TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert, TIdentityUaClientSecretsInsert,
TIdentityUaClientSecretsUpdate TIdentityUaClientSecretsUpdate
>; >;
[TableName.IdentityAccessToken]: Knex.CompositeTableType< [TableName.IdentityAccessToken]: KnexOriginal.CompositeTableType<
TIdentityAccessTokens, TIdentityAccessTokens,
TIdentityAccessTokensInsert, TIdentityAccessTokensInsert,
TIdentityAccessTokensUpdate TIdentityAccessTokensUpdate
>; >;
[TableName.IdentityOrgMembership]: Knex.CompositeTableType< [TableName.IdentityOrgMembership]: KnexOriginal.CompositeTableType<
TIdentityOrgMemberships, TIdentityOrgMemberships,
TIdentityOrgMembershipsInsert, TIdentityOrgMembershipsInsert,
TIdentityOrgMembershipsUpdate TIdentityOrgMembershipsUpdate
>; >;
[TableName.IdentityProjectMembership]: Knex.CompositeTableType< [TableName.IdentityProjectMembership]: KnexOriginal.CompositeTableType<
TIdentityProjectMemberships, TIdentityProjectMemberships,
TIdentityProjectMembershipsInsert, TIdentityProjectMembershipsInsert,
TIdentityProjectMembershipsUpdate TIdentityProjectMembershipsUpdate
>; >;
[TableName.IdentityProjectMembershipRole]: Knex.CompositeTableType< [TableName.IdentityProjectMembershipRole]: KnexOriginal.CompositeTableType<
TIdentityProjectMembershipRole, TIdentityProjectMembershipRole,
TIdentityProjectMembershipRoleInsert, TIdentityProjectMembershipRoleInsert,
TIdentityProjectMembershipRoleUpdate TIdentityProjectMembershipRoleUpdate
>; >;
[TableName.IdentityProjectAdditionalPrivilege]: Knex.CompositeTableType< [TableName.IdentityProjectAdditionalPrivilege]: KnexOriginal.CompositeTableType<
TIdentityProjectAdditionalPrivilege, TIdentityProjectAdditionalPrivilege,
TIdentityProjectAdditionalPrivilegeInsert, TIdentityProjectAdditionalPrivilegeInsert,
TIdentityProjectAdditionalPrivilegeUpdate TIdentityProjectAdditionalPrivilegeUpdate
>; >;
[TableName.AccessApprovalPolicy]: Knex.CompositeTableType< [TableName.AccessApprovalPolicy]: KnexOriginal.CompositeTableType<
TAccessApprovalPolicies, TAccessApprovalPolicies,
TAccessApprovalPoliciesInsert, TAccessApprovalPoliciesInsert,
TAccessApprovalPoliciesUpdate TAccessApprovalPoliciesUpdate
>; >;
[TableName.AccessApprovalPolicyApprover]: Knex.CompositeTableType< [TableName.AccessApprovalPolicyApprover]: KnexOriginal.CompositeTableType<
TAccessApprovalPoliciesApprovers, TAccessApprovalPoliciesApprovers,
TAccessApprovalPoliciesApproversInsert, TAccessApprovalPoliciesApproversInsert,
TAccessApprovalPoliciesApproversUpdate TAccessApprovalPoliciesApproversUpdate
>; >;
[TableName.AccessApprovalRequest]: Knex.CompositeTableType< [TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType<
TAccessApprovalRequests, TAccessApprovalRequests,
TAccessApprovalRequestsInsert, TAccessApprovalRequestsInsert,
TAccessApprovalRequestsUpdate TAccessApprovalRequestsUpdate
>; >;
[TableName.AccessApprovalRequestReviewer]: Knex.CompositeTableType< [TableName.AccessApprovalRequestReviewer]: KnexOriginal.CompositeTableType<
TAccessApprovalRequestsReviewers, TAccessApprovalRequestsReviewers,
TAccessApprovalRequestsReviewersInsert, TAccessApprovalRequestsReviewersInsert,
TAccessApprovalRequestsReviewersUpdate TAccessApprovalRequestsReviewersUpdate
>; >;
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>; [TableName.ScimToken]: KnexOriginal.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType< [TableName.SecretApprovalPolicy]: KnexOriginal.CompositeTableType<
TSecretApprovalPolicies, TSecretApprovalPolicies,
TSecretApprovalPoliciesInsert, TSecretApprovalPoliciesInsert,
TSecretApprovalPoliciesUpdate TSecretApprovalPoliciesUpdate
>; >;
[TableName.SecretApprovalPolicyApprover]: Knex.CompositeTableType< [TableName.SecretApprovalPolicyApprover]: KnexOriginal.CompositeTableType<
TSecretApprovalPoliciesApprovers, TSecretApprovalPoliciesApprovers,
TSecretApprovalPoliciesApproversInsert, TSecretApprovalPoliciesApproversInsert,
TSecretApprovalPoliciesApproversUpdate TSecretApprovalPoliciesApproversUpdate
>; >;
[TableName.SecretApprovalRequest]: Knex.CompositeTableType< [TableName.SecretApprovalRequest]: KnexOriginal.CompositeTableType<
TSecretApprovalRequests, TSecretApprovalRequests,
TSecretApprovalRequestsInsert, TSecretApprovalRequestsInsert,
TSecretApprovalRequestsUpdate TSecretApprovalRequestsUpdate
>; >;
[TableName.SecretApprovalRequestReviewer]: Knex.CompositeTableType< [TableName.SecretApprovalRequestReviewer]: KnexOriginal.CompositeTableType<
TSecretApprovalRequestsReviewers, TSecretApprovalRequestsReviewers,
TSecretApprovalRequestsReviewersInsert, TSecretApprovalRequestsReviewersInsert,
TSecretApprovalRequestsReviewersUpdate TSecretApprovalRequestsReviewersUpdate
>; >;
[TableName.SecretApprovalRequestSecret]: Knex.CompositeTableType< [TableName.SecretApprovalRequestSecret]: KnexOriginal.CompositeTableType<
TSecretApprovalRequestsSecrets, TSecretApprovalRequestsSecrets,
TSecretApprovalRequestsSecretsInsert, TSecretApprovalRequestsSecretsInsert,
TSecretApprovalRequestsSecretsUpdate TSecretApprovalRequestsSecretsUpdate
>; >;
[TableName.SecretApprovalRequestSecretTag]: Knex.CompositeTableType< [TableName.SecretApprovalRequestSecretTag]: KnexOriginal.CompositeTableType<
TSecretApprovalRequestSecretTags, TSecretApprovalRequestSecretTags,
TSecretApprovalRequestSecretTagsInsert, TSecretApprovalRequestSecretTagsInsert,
TSecretApprovalRequestSecretTagsUpdate TSecretApprovalRequestSecretTagsUpdate
>; >;
[TableName.SecretRotation]: Knex.CompositeTableType< [TableName.SecretRotation]: KnexOriginal.CompositeTableType<
TSecretRotations, TSecretRotations,
TSecretRotationsInsert, TSecretRotationsInsert,
TSecretRotationsUpdate TSecretRotationsUpdate
>; >;
[TableName.SecretRotationOutput]: Knex.CompositeTableType< [TableName.SecretRotationOutput]: KnexOriginal.CompositeTableType<
TSecretRotationOutputs, TSecretRotationOutputs,
TSecretRotationOutputsInsert, TSecretRotationOutputsInsert,
TSecretRotationOutputsUpdate TSecretRotationOutputsUpdate
>; >;
[TableName.Snapshot]: Knex.CompositeTableType<TSecretSnapshots, TSecretSnapshotsInsert, TSecretSnapshotsUpdate>; [TableName.Snapshot]: KnexOriginal.CompositeTableType<
[TableName.SnapshotSecret]: Knex.CompositeTableType< TSecretSnapshots,
TSecretSnapshotsInsert,
TSecretSnapshotsUpdate
>;
[TableName.SnapshotSecret]: KnexOriginal.CompositeTableType<
TSecretSnapshotSecrets, TSecretSnapshotSecrets,
TSecretSnapshotSecretsInsert, TSecretSnapshotSecretsInsert,
TSecretSnapshotSecretsUpdate TSecretSnapshotSecretsUpdate
>; >;
[TableName.SnapshotFolder]: Knex.CompositeTableType< [TableName.SnapshotFolder]: KnexOriginal.CompositeTableType<
TSecretSnapshotFolders, TSecretSnapshotFolders,
TSecretSnapshotFoldersInsert, TSecretSnapshotFoldersInsert,
TSecretSnapshotFoldersUpdate TSecretSnapshotFoldersUpdate
>; >;
[TableName.DynamicSecret]: Knex.CompositeTableType<TDynamicSecrets, TDynamicSecretsInsert, TDynamicSecretsUpdate>; [TableName.DynamicSecret]: KnexOriginal.CompositeTableType<
[TableName.DynamicSecretLease]: Knex.CompositeTableType< TDynamicSecrets,
TDynamicSecretsInsert,
TDynamicSecretsUpdate
>;
[TableName.DynamicSecretLease]: KnexOriginal.CompositeTableType<
TDynamicSecretLeases, TDynamicSecretLeases,
TDynamicSecretLeasesInsert, TDynamicSecretLeasesInsert,
TDynamicSecretLeasesUpdate TDynamicSecretLeasesUpdate
>; >;
[TableName.SamlConfig]: Knex.CompositeTableType<TSamlConfigs, TSamlConfigsInsert, TSamlConfigsUpdate>; [TableName.SamlConfig]: KnexOriginal.CompositeTableType<TSamlConfigs, TSamlConfigsInsert, TSamlConfigsUpdate>;
[TableName.LdapConfig]: Knex.CompositeTableType<TLdapConfigs, TLdapConfigsInsert, TLdapConfigsUpdate>; [TableName.OidcConfig]: KnexOriginal.CompositeTableType<TOidcConfigs, TOidcConfigsInsert, TOidcConfigsUpdate>;
[TableName.LdapGroupMap]: Knex.CompositeTableType<TLdapGroupMaps, TLdapGroupMapsInsert, TLdapGroupMapsUpdate>; [TableName.LdapConfig]: KnexOriginal.CompositeTableType<TLdapConfigs, TLdapConfigsInsert, TLdapConfigsUpdate>;
[TableName.OrgBot]: Knex.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>; [TableName.LdapGroupMap]: KnexOriginal.CompositeTableType<
[TableName.AuditLog]: Knex.CompositeTableType<TAuditLogs, TAuditLogsInsert, TAuditLogsUpdate>; TLdapGroupMaps,
[TableName.AuditLogStream]: Knex.CompositeTableType< TLdapGroupMapsInsert,
TLdapGroupMapsUpdate
>;
[TableName.OrgBot]: KnexOriginal.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>;
[TableName.AuditLog]: KnexOriginal.CompositeTableType<TAuditLogs, TAuditLogsInsert, TAuditLogsUpdate>;
[TableName.AuditLogStream]: KnexOriginal.CompositeTableType<
TAuditLogStreams, TAuditLogStreams,
TAuditLogStreamsInsert, TAuditLogStreamsInsert,
TAuditLogStreamsUpdate TAuditLogStreamsUpdate
>; >;
[TableName.GitAppInstallSession]: Knex.CompositeTableType< [TableName.GitAppInstallSession]: KnexOriginal.CompositeTableType<
TGitAppInstallSessions, TGitAppInstallSessions,
TGitAppInstallSessionsInsert, TGitAppInstallSessionsInsert,
TGitAppInstallSessionsUpdate TGitAppInstallSessionsUpdate
>; >;
[TableName.GitAppOrg]: Knex.CompositeTableType<TGitAppOrg, TGitAppOrgInsert, TGitAppOrgUpdate>; [TableName.GitAppOrg]: KnexOriginal.CompositeTableType<TGitAppOrg, TGitAppOrgInsert, TGitAppOrgUpdate>;
[TableName.SecretScanningGitRisk]: Knex.CompositeTableType< [TableName.SecretScanningGitRisk]: KnexOriginal.CompositeTableType<
TSecretScanningGitRisks, TSecretScanningGitRisks,
TSecretScanningGitRisksInsert, TSecretScanningGitRisksInsert,
TSecretScanningGitRisksUpdate TSecretScanningGitRisksUpdate
>; >;
[TableName.TrustedIps]: Knex.CompositeTableType<TTrustedIps, TTrustedIpsInsert, TTrustedIpsUpdate>; [TableName.TrustedIps]: KnexOriginal.CompositeTableType<TTrustedIps, TTrustedIpsInsert, TTrustedIpsUpdate>;
// Junction tables // Junction tables
[TableName.JnSecretTag]: Knex.CompositeTableType< [TableName.JnSecretTag]: KnexOriginal.CompositeTableType<
TSecretTagJunction, TSecretTagJunction,
TSecretTagJunctionInsert, TSecretTagJunctionInsert,
TSecretTagJunctionUpdate TSecretTagJunctionUpdate
>; >;
[TableName.SecretVersionTag]: Knex.CompositeTableType< [TableName.SecretVersionTag]: KnexOriginal.CompositeTableType<
TSecretVersionTagJunction, TSecretVersionTagJunction,
TSecretVersionTagJunctionInsert, TSecretVersionTagJunctionInsert,
TSecretVersionTagJunctionUpdate TSecretVersionTagJunctionUpdate
>; >;
// KMS service // KMS service
[TableName.KmsServerRootConfig]: Knex.CompositeTableType< [TableName.KmsServerRootConfig]: KnexOriginal.CompositeTableType<
TKmsRootConfig, TKmsRootConfig,
TKmsRootConfigInsert, TKmsRootConfigInsert,
TKmsRootConfigUpdate TKmsRootConfigUpdate
>; >;
[TableName.KmsKey]: Knex.CompositeTableType<TKmsKeys, TKmsKeysInsert, TKmsKeysUpdate>; [TableName.KmsKey]: KnexOriginal.CompositeTableType<TKmsKeys, TKmsKeysInsert, TKmsKeysUpdate>;
[TableName.KmsKeyVersion]: Knex.CompositeTableType<TKmsKeyVersions, TKmsKeyVersionsInsert, TKmsKeyVersionsUpdate>; [TableName.KmsKeyVersion]: KnexOriginal.CompositeTableType<
TKmsKeyVersions,
TKmsKeyVersionsInsert,
TKmsKeyVersionsUpdate
>;
} }
} }

View File

@ -1,8 +1,38 @@
import knex from "knex"; import knex, { Knex } from "knex";
export type TDbClient = ReturnType<typeof initDbConnection>; export type TDbClient = ReturnType<typeof initDbConnection>;
export const initDbConnection = ({ dbConnectionUri, dbRootCert }: { dbConnectionUri: string; dbRootCert?: string }) => { export const initDbConnection = ({
const db = knex({ dbConnectionUri,
dbRootCert,
readReplicas = []
}: {
dbConnectionUri: string;
dbRootCert?: string;
readReplicas?: {
dbConnectionUri: string;
dbRootCert?: string;
}[];
}) => {
// akhilmhdh: the default Knex is knex.Knex<any, any[]>. but when assigned with knex({<config>}) the value is knex.Knex<any, unknown[]>
// this was causing issue with files like `snapshot-dal` `findRecursivelySnapshots` this i am explicitly putting the any and unknown[]
// eslint-disable-next-line
let db: Knex<any, unknown[]>;
// eslint-disable-next-line
let readReplicaDbs: Knex<any, unknown[]>[];
// @ts-expect-error the querybuilder type is expected but our intension is to return a knex instance
knex.QueryBuilder.extend("primaryNode", () => {
return db;
});
// @ts-expect-error the querybuilder type is expected but our intension is to return a knex instance
knex.QueryBuilder.extend("replicaNode", () => {
if (!readReplicaDbs.length) return db;
const selectedReplica = readReplicaDbs[Math.floor(Math.random() * readReplicaDbs.length)];
return selectedReplica;
});
db = knex({
client: "pg", client: "pg",
connection: { connection: {
connectionString: dbConnectionUri, connectionString: dbConnectionUri,
@ -22,5 +52,21 @@ export const initDbConnection = ({ dbConnectionUri, dbRootCert }: { dbConnection
} }
}); });
readReplicaDbs = readReplicas.map((el) => {
const replicaDbCertificate = el.dbRootCert || dbRootCert;
return knex({
client: "pg",
connection: {
connectionString: el.dbConnectionUri,
ssl: replicaDbCertificate
? {
rejectUnauthorized: true,
ca: Buffer.from(replicaDbCertificate, "base64").toString("ascii")
}
: false
}
});
});
return db; return db;
}; };

View File

@ -0,0 +1,49 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.OidcConfig))) {
await knex.schema.createTable(TableName.OidcConfig, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.string("discoveryURL");
tb.string("issuer");
tb.string("authorizationEndpoint");
tb.string("jwksUri");
tb.string("tokenEndpoint");
tb.string("userinfoEndpoint");
tb.text("encryptedClientId").notNullable();
tb.string("configurationType").notNullable();
tb.string("clientIdIV").notNullable();
tb.string("clientIdTag").notNullable();
tb.text("encryptedClientSecret").notNullable();
tb.string("clientSecretIV").notNullable();
tb.string("clientSecretTag").notNullable();
tb.string("allowedEmailDomains").nullable();
tb.boolean("isActive").notNullable();
tb.timestamps(true, true, true);
tb.uuid("orgId").notNullable().unique();
tb.foreign("orgId").references("id").inTable(TableName.Organization);
});
}
if (await knex.schema.hasTable(TableName.SuperAdmin)) {
if (!(await knex.schema.hasColumn(TableName.SuperAdmin, "trustOidcEmails"))) {
await knex.schema.alterTable(TableName.SuperAdmin, (tb) => {
tb.boolean("trustOidcEmails").defaultTo(false);
});
}
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.OidcConfig);
if (await knex.schema.hasTable(TableName.SuperAdmin)) {
if (await knex.schema.hasColumn(TableName.SuperAdmin, "trustOidcEmails")) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.dropColumn("trustOidcEmails");
});
}
}
}

View File

@ -0,0 +1,27 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
const DEFAULT_AUTH_ORG_ID_FIELD = "defaultAuthOrgId";
export async function up(knex: Knex): Promise<void> {
const hasDefaultOrgColumn = await knex.schema.hasColumn(TableName.SuperAdmin, DEFAULT_AUTH_ORG_ID_FIELD);
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
if (!hasDefaultOrgColumn) {
t.uuid(DEFAULT_AUTH_ORG_ID_FIELD).nullable();
t.foreign(DEFAULT_AUTH_ORG_ID_FIELD).references("id").inTable(TableName.Organization).onDelete("SET NULL");
}
});
}
export async function down(knex: Knex): Promise<void> {
const hasDefaultOrgColumn = await knex.schema.hasColumn(TableName.SuperAdmin, DEFAULT_AUTH_ORG_ID_FIELD);
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
if (hasDefaultOrgColumn) {
t.dropForeign([DEFAULT_AUTH_ORG_ID_FIELD]);
t.dropColumn(DEFAULT_AUTH_ORG_ID_FIELD);
}
});
}

View File

@ -0,0 +1,24 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.Certificate)) {
const hasAltNamesColumn = await knex.schema.hasColumn(TableName.Certificate, "altNames");
if (!hasAltNamesColumn) {
await knex.schema.alterTable(TableName.Certificate, (t) => {
t.string("altNames").defaultTo("");
});
}
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.Certificate)) {
if (await knex.schema.hasColumn(TableName.Certificate, "altNames")) {
await knex.schema.alterTable(TableName.Certificate, (t) => {
t.dropColumn("altNames");
});
}
}
}

View File

@ -0,0 +1,35 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasAwsAssumeRoleCipherText = await knex.schema.hasColumn(
TableName.IntegrationAuth,
"awsAssumeIamRoleArnCipherText"
);
const hasAwsAssumeRoleIV = await knex.schema.hasColumn(TableName.IntegrationAuth, "awsAssumeIamRoleArnIV");
const hasAwsAssumeRoleTag = await knex.schema.hasColumn(TableName.IntegrationAuth, "awsAssumeIamRoleArnTag");
if (await knex.schema.hasTable(TableName.IntegrationAuth)) {
await knex.schema.alterTable(TableName.IntegrationAuth, (t) => {
if (!hasAwsAssumeRoleCipherText) t.text("awsAssumeIamRoleArnCipherText");
if (!hasAwsAssumeRoleIV) t.text("awsAssumeIamRoleArnIV");
if (!hasAwsAssumeRoleTag) t.text("awsAssumeIamRoleArnTag");
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasAwsAssumeRoleCipherText = await knex.schema.hasColumn(
TableName.IntegrationAuth,
"awsAssumeIamRoleArnCipherText"
);
const hasAwsAssumeRoleIV = await knex.schema.hasColumn(TableName.IntegrationAuth, "awsAssumeIamRoleArnIV");
const hasAwsAssumeRoleTag = await knex.schema.hasColumn(TableName.IntegrationAuth, "awsAssumeIamRoleArnTag");
if (await knex.schema.hasTable(TableName.IntegrationAuth)) {
await knex.schema.alterTable(TableName.IntegrationAuth, (t) => {
if (hasAwsAssumeRoleCipherText) t.dropColumn("awsAssumeIamRoleArnCipherText");
if (hasAwsAssumeRoleIV) t.dropColumn("awsAssumeIamRoleArnIV");
if (hasAwsAssumeRoleTag) t.dropColumn("awsAssumeIamRoleArnTag");
});
}
}

View File

@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.LdapConfig, "uniqueUserAttribute"))) {
await knex.schema.alterTable(TableName.LdapConfig, (tb) => {
tb.string("uniqueUserAttribute").notNullable().defaultTo("");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.LdapConfig, "uniqueUserAttribute")) {
await knex.schema.alterTable(TableName.LdapConfig, (t) => {
t.dropColumn("uniqueUserAttribute");
});
}
}

View File

@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.Project, "auditLogsRetentionDays"))) {
await knex.schema.alterTable(TableName.Project, (tb) => {
tb.integer("auditLogsRetentionDays").nullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.Project, "auditLogsRetentionDays")) {
await knex.schema.alterTable(TableName.Project, (t) => {
t.dropColumn("auditLogsRetentionDays");
});
}
}

View File

@ -0,0 +1,12 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
await createOnUpdateTrigger(knex, TableName.OidcConfig);
}
export async function down(knex: Knex): Promise<void> {
await dropOnUpdateTrigger(knex, TableName.OidcConfig);
}

View File

@ -19,7 +19,8 @@ export const CertificatesSchema = z.object({
notBefore: z.date(), notBefore: z.date(),
notAfter: z.date(), notAfter: z.date(),
revokedAt: z.date().nullable().optional(), revokedAt: z.date().nullable().optional(),
revocationReason: z.number().nullable().optional() revocationReason: z.number().nullable().optional(),
altNames: z.string().default("").nullable().optional()
}); });
export type TCertificates = z.infer<typeof CertificatesSchema>; export type TCertificates = z.infer<typeof CertificatesSchema>;

View File

@ -43,6 +43,7 @@ export * from "./kms-root-config";
export * from "./ldap-configs"; export * from "./ldap-configs";
export * from "./ldap-group-maps"; export * from "./ldap-group-maps";
export * from "./models"; export * from "./models";
export * from "./oidc-configs";
export * from "./org-bots"; export * from "./org-bots";
export * from "./org-memberships"; export * from "./org-memberships";
export * from "./org-roles"; export * from "./org-roles";

View File

@ -29,7 +29,10 @@ export const IntegrationAuthsSchema = z.object({
keyEncoding: z.string(), keyEncoding: z.string(),
projectId: z.string(), projectId: z.string(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
awsAssumeIamRoleArnCipherText: z.string().nullable().optional(),
awsAssumeIamRoleArnIV: z.string().nullable().optional(),
awsAssumeIamRoleArnTag: z.string().nullable().optional()
}); });
export type TIntegrationAuths = z.infer<typeof IntegrationAuthsSchema>; export type TIntegrationAuths = z.infer<typeof IntegrationAuthsSchema>;

View File

@ -26,7 +26,8 @@ export const LdapConfigsSchema = z.object({
updatedAt: z.date(), updatedAt: z.date(),
groupSearchBase: z.string().default(""), groupSearchBase: z.string().default(""),
groupSearchFilter: z.string().default(""), groupSearchFilter: z.string().default(""),
searchFilter: z.string().default("") searchFilter: z.string().default(""),
uniqueUserAttribute: z.string().default("")
}); });
export type TLdapConfigs = z.infer<typeof LdapConfigsSchema>; export type TLdapConfigs = z.infer<typeof LdapConfigsSchema>;

View File

@ -78,6 +78,7 @@ export enum TableName {
SecretRotationOutput = "secret_rotation_outputs", SecretRotationOutput = "secret_rotation_outputs",
SamlConfig = "saml_configs", SamlConfig = "saml_configs",
LdapConfig = "ldap_configs", LdapConfig = "ldap_configs",
OidcConfig = "oidc_configs",
LdapGroupMap = "ldap_group_maps", LdapGroupMap = "ldap_group_maps",
AuditLog = "audit_logs", AuditLog = "audit_logs",
AuditLogStream = "audit_log_streams", AuditLogStream = "audit_log_streams",

View File

@ -0,0 +1,34 @@
// 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 { TImmutableDBKeys } from "./models";
export const OidcConfigsSchema = z.object({
id: z.string().uuid(),
discoveryURL: z.string().nullable().optional(),
issuer: z.string().nullable().optional(),
authorizationEndpoint: z.string().nullable().optional(),
jwksUri: z.string().nullable().optional(),
tokenEndpoint: z.string().nullable().optional(),
userinfoEndpoint: z.string().nullable().optional(),
encryptedClientId: z.string(),
configurationType: z.string(),
clientIdIV: z.string(),
clientIdTag: z.string(),
encryptedClientSecret: z.string(),
clientSecretIV: z.string(),
clientSecretTag: z.string(),
allowedEmailDomains: z.string().nullable().optional(),
isActive: z.boolean(),
createdAt: z.date(),
updatedAt: z.date(),
orgId: z.string().uuid()
});
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
export type TOidcConfigsInsert = Omit<z.input<typeof OidcConfigsSchema>, TImmutableDBKeys>;
export type TOidcConfigsUpdate = Partial<Omit<z.input<typeof OidcConfigsSchema>, TImmutableDBKeys>>;

View File

@ -17,8 +17,9 @@ export const ProjectsSchema = z.object({
updatedAt: z.date(), updatedAt: z.date(),
version: z.number().default(1), version: z.number().default(1),
upgradeStatus: z.string().nullable().optional(), upgradeStatus: z.string().nullable().optional(),
pitVersionLimit: z.number().default(10),
kmsCertificateKeyId: z.string().uuid().nullable().optional(), kmsCertificateKeyId: z.string().uuid().nullable().optional(),
pitVersionLimit: z.number().default(10) auditLogsRetentionDays: z.number().nullable().optional()
}); });
export type TProjects = z.infer<typeof ProjectsSchema>; export type TProjects = z.infer<typeof ProjectsSchema>;

View File

@ -16,7 +16,9 @@ export const SuperAdminSchema = z.object({
allowedSignUpDomain: z.string().nullable().optional(), allowedSignUpDomain: z.string().nullable().optional(),
instanceId: z.string().uuid().default("00000000-0000-0000-0000-000000000000"), instanceId: z.string().uuid().default("00000000-0000-0000-0000-000000000000"),
trustSamlEmails: z.boolean().default(false).nullable().optional(), trustSamlEmails: z.boolean().default(false).nullable().optional(),
trustLdapEmails: z.boolean().default(false).nullable().optional() trustLdapEmails: z.boolean().default(false).nullable().optional(),
trustOidcEmails: z.boolean().default(false).nullable().optional(),
defaultAuthOrgId: z.string().uuid().nullable().optional()
}); });
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>; export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;

View File

@ -8,6 +8,7 @@ import { registerGroupRouter } from "./group-router";
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router"; import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
import { registerLdapRouter } from "./ldap-router"; import { registerLdapRouter } from "./ldap-router";
import { registerLicenseRouter } from "./license-router"; import { registerLicenseRouter } from "./license-router";
import { registerOidcRouter } from "./oidc-router";
import { registerOrgRoleRouter } from "./org-role-router"; import { registerOrgRoleRouter } from "./org-role-router";
import { registerProjectRoleRouter } from "./project-role-router"; import { registerProjectRoleRouter } from "./project-role-router";
import { registerProjectRouter } from "./project-router"; import { registerProjectRouter } from "./project-router";
@ -64,7 +65,14 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
{ prefix: "/pki" } { prefix: "/pki" }
); );
await server.register(registerSamlRouter, { prefix: "/sso" }); await server.register(
async (ssoRouter) => {
await ssoRouter.register(registerSamlRouter);
await ssoRouter.register(registerOidcRouter, { prefix: "/oidc" });
},
{ prefix: "/sso" }
);
await server.register(registerScimRouter, { prefix: "/scim" }); await server.register(registerScimRouter, { prefix: "/scim" });
await server.register(registerLdapRouter, { prefix: "/ldap" }); await server.register(registerLdapRouter, { prefix: "/ldap" });
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" }); await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });

View File

@ -70,10 +70,13 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
groups = await searchGroups(ldapConfig, groupSearchFilter, ldapConfig.groupSearchBase); groups = await searchGroups(ldapConfig, groupSearchFilter, ldapConfig.groupSearchBase);
} }
const externalId = ldapConfig.uniqueUserAttribute ? user[ldapConfig.uniqueUserAttribute] : user.uidNumber;
const username = ldapConfig.uniqueUserAttribute ? externalId : user.uid;
const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({ const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({
externalId,
username,
ldapConfigId: ldapConfig.id, ldapConfigId: ldapConfig.id,
externalId: user.uidNumber,
username: user.uid,
firstName: user.givenName ?? user.cn ?? "", firstName: user.givenName ?? user.cn ?? "",
lastName: user.sn ?? "", lastName: user.sn ?? "",
email: user.mail, email: user.mail,
@ -138,6 +141,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
url: z.string(), url: z.string(),
bindDN: z.string(), bindDN: z.string(),
bindPass: z.string(), bindPass: z.string(),
uniqueUserAttribute: z.string(),
searchBase: z.string(), searchBase: z.string(),
searchFilter: z.string(), searchFilter: z.string(),
groupSearchBase: z.string(), groupSearchBase: z.string(),
@ -172,6 +176,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
url: z.string().trim(), url: z.string().trim(),
bindDN: z.string().trim(), bindDN: z.string().trim(),
bindPass: z.string().trim(), bindPass: z.string().trim(),
uniqueUserAttribute: z.string().trim().default("uidNumber"),
searchBase: z.string().trim(), searchBase: z.string().trim(),
searchFilter: z.string().trim().default("(uid={{username}})"), searchFilter: z.string().trim().default("(uid={{username}})"),
groupSearchBase: z.string().trim(), groupSearchBase: z.string().trim(),
@ -213,6 +218,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
url: z.string().trim(), url: z.string().trim(),
bindDN: z.string().trim(), bindDN: z.string().trim(),
bindPass: z.string().trim(), bindPass: z.string().trim(),
uniqueUserAttribute: z.string().trim(),
searchBase: z.string().trim(), searchBase: z.string().trim(),
searchFilter: z.string().trim(), searchFilter: z.string().trim(),
groupSearchBase: z.string().trim(), groupSearchBase: z.string().trim(),

View File

@ -0,0 +1,355 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
// All the any rules are disabled because passport typesense with fastify is really poor
import { Authenticator, Strategy } from "@fastify/passport";
import fastifySession from "@fastify/session";
import RedisStore from "connect-redis";
import { Redis } from "ioredis";
import { z } from "zod";
import { OidcConfigsSchema } from "@app/db/schemas/oidc-configs";
import { OIDCConfigurationType } from "@app/ee/services/oidc/oidc-config-types";
import { getConfig } from "@app/lib/config/env";
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerOidcRouter = async (server: FastifyZodProvider) => {
const appCfg = getConfig();
const redis = new Redis(appCfg.REDIS_URL);
const passport = new Authenticator({ key: "oidc", userProperty: "passportUser" });
/*
- OIDC protocol cannot work without sessions: https://github.com/panva/node-openid-client/issues/190
- Current redis usage is not ideal and will eventually have to be refactored to use a better structure
- Fastify session <> Redis structure is based on the ff: https://github.com/fastify/session/blob/master/examples/redis.js
*/
const redisStore = new RedisStore({
client: redis,
prefix: "oidc-session:",
ttl: 600 // 10 minutes
});
await server.register(fastifySession, {
secret: appCfg.COOKIE_SECRET_SIGN_KEY,
store: redisStore,
cookie: {
secure: appCfg.HTTPS_ENABLED,
sameSite: "lax" // we want cookies to be sent to Infisical in redirects originating from IDP server
}
});
await server.register(passport.initialize());
await server.register(passport.secureSession());
// redirect to IDP for login
server.route({
url: "/login",
method: "GET",
config: {
rateLimit: authRateLimit
},
schema: {
querystring: z.object({
orgSlug: z.string().trim(),
callbackPort: z.string().trim().optional()
})
},
preValidation: [
async (req, res) => {
const { orgSlug, callbackPort } = req.query;
// ensure fresh session state per login attempt
await req.session.regenerate();
req.session.set<any>("oidcOrgSlug", orgSlug);
if (callbackPort) {
req.session.set<any>("callbackPort", callbackPort);
}
const oidcStrategy = await server.services.oidc.getOrgAuthStrategy(orgSlug, callbackPort);
return (
passport.authenticate(oidcStrategy as Strategy, {
scope: "profile email openid"
}) as any
)(req, res);
}
],
handler: () => {}
});
// callback route after login from IDP
server.route({
url: "/callback",
method: "GET",
preValidation: [
async (req, res) => {
const oidcOrgSlug = req.session.get<any>("oidcOrgSlug");
const callbackPort = req.session.get<any>("callbackPort");
const oidcStrategy = await server.services.oidc.getOrgAuthStrategy(oidcOrgSlug, callbackPort);
return (
passport.authenticate(oidcStrategy as Strategy, {
failureRedirect: "/api/v1/sso/oidc/login/error",
session: false,
failureMessage: true
}) as any
)(req, res);
}
],
handler: async (req, res) => {
await req.session.destroy();
if (req.passportUser.isUserCompleted) {
return res.redirect(
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
);
}
// signup
return res.redirect(
`${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
);
}
});
server.route({
url: "/login/error",
method: "GET",
handler: async (req, res) => {
await req.session.destroy();
return res.status(500).send({
error: "Authentication error",
details: req.query
});
}
});
server.route({
url: "/config",
method: "GET",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({
orgSlug: z.string().trim()
}),
response: {
200: OidcConfigsSchema.pick({
id: true,
issuer: true,
authorizationEndpoint: true,
jwksUri: true,
tokenEndpoint: true,
userinfoEndpoint: true,
configurationType: true,
discoveryURL: true,
isActive: true,
orgId: true,
allowedEmailDomains: true
}).extend({
clientId: z.string(),
clientSecret: z.string()
})
}
},
handler: async (req) => {
const { orgSlug } = req.query;
const oidc = await server.services.oidc.getOidc({
orgSlug,
type: "external",
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod
});
return oidc;
}
});
server.route({
method: "PATCH",
url: "/config",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z
.object({
allowedEmailDomains: z
.string()
.trim()
.optional()
.default("")
.transform((data) => {
if (data === "") return "";
// Trim each ID and join with ', ' to ensure formatting
return data
.split(",")
.map((id) => id.trim())
.join(", ");
}),
discoveryURL: z.string().trim(),
configurationType: z.nativeEnum(OIDCConfigurationType),
issuer: z.string().trim(),
authorizationEndpoint: z.string().trim(),
jwksUri: z.string().trim(),
tokenEndpoint: z.string().trim(),
userinfoEndpoint: z.string().trim(),
clientId: z.string().trim(),
clientSecret: z.string().trim(),
isActive: z.boolean()
})
.partial()
.merge(z.object({ orgSlug: z.string() })),
response: {
200: OidcConfigsSchema.pick({
id: true,
issuer: true,
authorizationEndpoint: true,
configurationType: true,
discoveryURL: true,
jwksUri: true,
tokenEndpoint: true,
userinfoEndpoint: true,
orgId: true,
allowedEmailDomains: true,
isActive: true
})
}
},
handler: async (req) => {
const oidc = await server.services.oidc.updateOidcCfg({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return oidc;
}
});
server.route({
method: "POST",
url: "/config",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z
.object({
allowedEmailDomains: z
.string()
.trim()
.optional()
.default("")
.transform((data) => {
if (data === "") return "";
// Trim each ID and join with ', ' to ensure formatting
return data
.split(",")
.map((id) => id.trim())
.join(", ");
}),
configurationType: z.nativeEnum(OIDCConfigurationType),
issuer: z.string().trim().optional().default(""),
discoveryURL: z.string().trim().optional().default(""),
authorizationEndpoint: z.string().trim().optional().default(""),
jwksUri: z.string().trim().optional().default(""),
tokenEndpoint: z.string().trim().optional().default(""),
userinfoEndpoint: z.string().trim().optional().default(""),
clientId: z.string().trim(),
clientSecret: z.string().trim(),
isActive: z.boolean(),
orgSlug: z.string().trim()
})
.superRefine((data, ctx) => {
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
if (!data.issuer) {
ctx.addIssue({
path: ["issuer"],
message: "Issuer is required",
code: z.ZodIssueCode.custom
});
}
if (!data.authorizationEndpoint) {
ctx.addIssue({
path: ["authorizationEndpoint"],
message: "Authorization endpoint is required",
code: z.ZodIssueCode.custom
});
}
if (!data.jwksUri) {
ctx.addIssue({
path: ["jwksUri"],
message: "JWKS URI is required",
code: z.ZodIssueCode.custom
});
}
if (!data.tokenEndpoint) {
ctx.addIssue({
path: ["tokenEndpoint"],
message: "Token endpoint is required",
code: z.ZodIssueCode.custom
});
}
if (!data.userinfoEndpoint) {
ctx.addIssue({
path: ["userinfoEndpoint"],
message: "Userinfo endpoint is required",
code: z.ZodIssueCode.custom
});
}
} else {
// eslint-disable-next-line no-lonely-if
if (!data.discoveryURL) {
ctx.addIssue({
path: ["discoveryURL"],
message: "Discovery URL is required",
code: z.ZodIssueCode.custom
});
}
}
}),
response: {
200: OidcConfigsSchema.pick({
id: true,
issuer: true,
authorizationEndpoint: true,
configurationType: true,
discoveryURL: true,
jwksUri: true,
tokenEndpoint: true,
userinfoEndpoint: true,
orgId: true,
isActive: true,
allowedEmailDomains: true
})
}
},
handler: async (req) => {
const oidc = await server.services.oidc.createOidcCfg({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return oidc;
}
});
};

View File

@ -32,7 +32,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
const findById = async (id: string, tx?: Knex) => { const findById = async (id: string, tx?: Knex) => {
try { try {
const doc = await accessApprovalPolicyFindQuery(tx || db, { const doc = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), {
[`${TableName.AccessApprovalPolicy}.id` as "id"]: id [`${TableName.AccessApprovalPolicy}.id` as "id"]: id
}); });
const formatedDoc = mergeOneToManyRelation( const formatedDoc = mergeOneToManyRelation(
@ -54,7 +54,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
const find = async (filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>, tx?: Knex) => { const find = async (filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>, tx?: Knex) => {
try { try {
const docs = await accessApprovalPolicyFindQuery(tx || db, filter); const docs = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), filter);
const formatedDoc = mergeOneToManyRelation( const formatedDoc = mergeOneToManyRelation(
docs, docs,
"id", "id",

View File

@ -14,7 +14,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
const findRequestsWithPrivilegeByPolicyIds = async (policyIds: string[]) => { const findRequestsWithPrivilegeByPolicyIds = async (policyIds: string[]) => {
try { try {
const docs = await db(TableName.AccessApprovalRequest) const docs = await db
.replicaNode()(TableName.AccessApprovalRequest)
.whereIn(`${TableName.AccessApprovalRequest}.policyId`, policyIds) .whereIn(`${TableName.AccessApprovalRequest}.policyId`, policyIds)
.leftJoin( .leftJoin(
@ -170,7 +171,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
const findById = async (id: string, tx?: Knex) => { const findById = async (id: string, tx?: Knex) => {
try { try {
const sql = findQuery({ [`${TableName.AccessApprovalRequest}.id` as "id"]: id }, tx || db); const sql = findQuery({ [`${TableName.AccessApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode());
const docs = await sql; const docs = await sql;
const formatedDoc = sqlNestRelationships({ const formatedDoc = sqlNestRelationships({
data: docs, data: docs,
@ -207,7 +208,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
const getCount = async ({ projectId }: { projectId: string }) => { const getCount = async ({ projectId }: { projectId: string }) => {
try { try {
const accessRequests = await db(TableName.AccessApprovalRequest) const accessRequests = await db
.replicaNode()(TableName.AccessApprovalRequest)
.leftJoin( .leftJoin(
TableName.AccessApprovalPolicy, TableName.AccessApprovalPolicy,
`${TableName.AccessApprovalRequest}.policyId`, `${TableName.AccessApprovalRequest}.policyId`,

View File

@ -27,7 +27,7 @@ export const auditLogDALFactory = (db: TDbClient) => {
tx?: Knex tx?: Knex
) => { ) => {
try { try {
const sqlQuery = (tx || db)(TableName.AuditLog) const sqlQuery = (tx || db.replicaNode())(TableName.AuditLog)
.where( .where(
stripUndefinedInWhere({ stripUndefinedInWhere({
projectId, projectId,

View File

@ -45,18 +45,29 @@ export const auditLogQueueServiceFactory = ({
const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data; const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
let { orgId } = job.data; let { orgId } = job.data;
const MS_IN_DAY = 24 * 60 * 60 * 1000; const MS_IN_DAY = 24 * 60 * 60 * 1000;
let project;
if (!orgId) { if (!orgId) {
// it will never be undefined for both org and project id // it will never be undefined for both org and project id
// TODO(akhilmhdh): use caching here in dal to avoid db calls // TODO(akhilmhdh): use caching here in dal to avoid db calls
const project = await projectDAL.findById(projectId as string); project = await projectDAL.findById(projectId as string);
orgId = project.orgId; orgId = project.orgId;
} }
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
const ttl = plan.auditLogsRetentionDays * MS_IN_DAY; if (plan.auditLogsRetentionDays === 0) {
// skip inserting if audit log retention is 0 meaning its not supported // skip inserting if audit log retention is 0 meaning its not supported
if (ttl === 0) return; return;
}
// For project actions, set TTL to project-level audit log retention config
// This condition ensures that the plan's audit log retention days cannot be bypassed
const ttlInDays =
project?.auditLogsRetentionDays && project.auditLogsRetentionDays < plan.auditLogsRetentionDays
? project.auditLogsRetentionDays
: plan.auditLogsRetentionDays;
const ttl = ttlInDays * MS_IN_DAY;
const auditLog = await auditLogDAL.create({ const auditLog = await auditLogDAL.create({
actor: actor.type, actor: actor.type,

View File

@ -65,25 +65,31 @@ export enum EventType {
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth", ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth", UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth", GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth",
REVOKE_IDENTITY_UNIVERSAL_AUTH = "revoke-identity-universal-auth",
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth", LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth", ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth", UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth", GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
REVOKE_IDENTITY_KUBERNETES_AUTH = "revoke-identity-kubernetes-auth",
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret", CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret", REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret", GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID = "get-identity-universal-auth-client-secret-by-id",
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth", LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth", ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth", UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth", GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth", LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth", ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth", UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth", GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth", LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth", ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth", UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth", GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
REVOKE_IDENTITY_AZURE_AUTH = "revoke-identity-azure-auth",
CREATE_ENVIRONMENT = "create-environment", CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment", UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment", DELETE_ENVIRONMENT = "delete-environment",
@ -434,6 +440,13 @@ interface GetIdentityUniversalAuthEvent {
}; };
} }
interface DeleteIdentityUniversalAuthEvent {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH;
metadata: {
identityId: string;
};
}
interface LoginIdentityKubernetesAuthEvent { interface LoginIdentityKubernetesAuthEvent {
type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH; type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH;
metadata: { metadata: {
@ -457,6 +470,13 @@ interface AddIdentityKubernetesAuthEvent {
}; };
} }
interface DeleteIdentityKubernetesAuthEvent {
type: EventType.REVOKE_IDENTITY_KUBERNETES_AUTH;
metadata: {
identityId: string;
};
}
interface UpdateIdentityKubernetesAuthEvent { interface UpdateIdentityKubernetesAuthEvent {
type: EventType.UPDATE_IDENTITY_KUBENETES_AUTH; type: EventType.UPDATE_IDENTITY_KUBENETES_AUTH;
metadata: { metadata: {
@ -493,6 +513,14 @@ interface GetIdentityUniversalAuthClientSecretsEvent {
}; };
} }
interface GetIdentityUniversalAuthClientSecretByIdEvent {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID;
metadata: {
identityId: string;
clientSecretId: string;
};
}
interface RevokeIdentityUniversalAuthClientSecretEvent { interface RevokeIdentityUniversalAuthClientSecretEvent {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET; type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET;
metadata: { metadata: {
@ -525,6 +553,13 @@ interface AddIdentityGcpAuthEvent {
}; };
} }
interface DeleteIdentityGcpAuthEvent {
type: EventType.REVOKE_IDENTITY_GCP_AUTH;
metadata: {
identityId: string;
};
}
interface UpdateIdentityGcpAuthEvent { interface UpdateIdentityGcpAuthEvent {
type: EventType.UPDATE_IDENTITY_GCP_AUTH; type: EventType.UPDATE_IDENTITY_GCP_AUTH;
metadata: { metadata: {
@ -570,6 +605,13 @@ interface AddIdentityAwsAuthEvent {
}; };
} }
interface DeleteIdentityAwsAuthEvent {
type: EventType.REVOKE_IDENTITY_AWS_AUTH;
metadata: {
identityId: string;
};
}
interface UpdateIdentityAwsAuthEvent { interface UpdateIdentityAwsAuthEvent {
type: EventType.UPDATE_IDENTITY_AWS_AUTH; type: EventType.UPDATE_IDENTITY_AWS_AUTH;
metadata: { metadata: {
@ -613,6 +655,13 @@ interface AddIdentityAzureAuthEvent {
}; };
} }
interface DeleteIdentityAzureAuthEvent {
type: EventType.REVOKE_IDENTITY_AZURE_AUTH;
metadata: {
identityId: string;
};
}
interface UpdateIdentityAzureAuthEvent { interface UpdateIdentityAzureAuthEvent {
type: EventType.UPDATE_IDENTITY_AZURE_AUTH; type: EventType.UPDATE_IDENTITY_AZURE_AUTH;
metadata: { metadata: {
@ -1003,24 +1052,30 @@ export type Event =
| LoginIdentityUniversalAuthEvent | LoginIdentityUniversalAuthEvent
| AddIdentityUniversalAuthEvent | AddIdentityUniversalAuthEvent
| UpdateIdentityUniversalAuthEvent | UpdateIdentityUniversalAuthEvent
| DeleteIdentityUniversalAuthEvent
| GetIdentityUniversalAuthEvent | GetIdentityUniversalAuthEvent
| LoginIdentityKubernetesAuthEvent | LoginIdentityKubernetesAuthEvent
| DeleteIdentityKubernetesAuthEvent
| AddIdentityKubernetesAuthEvent | AddIdentityKubernetesAuthEvent
| UpdateIdentityKubernetesAuthEvent | UpdateIdentityKubernetesAuthEvent
| GetIdentityKubernetesAuthEvent | GetIdentityKubernetesAuthEvent
| CreateIdentityUniversalAuthClientSecretEvent | CreateIdentityUniversalAuthClientSecretEvent
| GetIdentityUniversalAuthClientSecretsEvent | GetIdentityUniversalAuthClientSecretsEvent
| GetIdentityUniversalAuthClientSecretByIdEvent
| RevokeIdentityUniversalAuthClientSecretEvent | RevokeIdentityUniversalAuthClientSecretEvent
| LoginIdentityGcpAuthEvent | LoginIdentityGcpAuthEvent
| AddIdentityGcpAuthEvent | AddIdentityGcpAuthEvent
| DeleteIdentityGcpAuthEvent
| UpdateIdentityGcpAuthEvent | UpdateIdentityGcpAuthEvent
| GetIdentityGcpAuthEvent | GetIdentityGcpAuthEvent
| LoginIdentityAwsAuthEvent | LoginIdentityAwsAuthEvent
| AddIdentityAwsAuthEvent | AddIdentityAwsAuthEvent
| UpdateIdentityAwsAuthEvent | UpdateIdentityAwsAuthEvent
| GetIdentityAwsAuthEvent | GetIdentityAwsAuthEvent
| DeleteIdentityAwsAuthEvent
| LoginIdentityAzureAuthEvent | LoginIdentityAzureAuthEvent
| AddIdentityAzureAuthEvent | AddIdentityAzureAuthEvent
| DeleteIdentityAzureAuthEvent
| UpdateIdentityAzureAuthEvent | UpdateIdentityAzureAuthEvent
| GetIdentityAzureAuthEvent | GetIdentityAzureAuthEvent
| CreateEnvironmentEvent | CreateEnvironmentEvent

View File

@ -12,7 +12,10 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
const countLeasesForDynamicSecret = async (dynamicSecretId: string, tx?: Knex) => { const countLeasesForDynamicSecret = async (dynamicSecretId: string, tx?: Knex) => {
try { try {
const doc = await (tx || db)(TableName.DynamicSecretLease).count("*").where({ dynamicSecretId }).first(); const doc = await (tx || db.replicaNode())(TableName.DynamicSecretLease)
.count("*")
.where({ dynamicSecretId })
.first();
return parseInt(doc || "0", 10); return parseInt(doc || "0", 10);
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "DynamicSecretCountLeases" }); throw new DatabaseError({ error, name: "DynamicSecretCountLeases" });
@ -21,7 +24,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
const findById = async (id: string, tx?: Knex) => { const findById = async (id: string, tx?: Knex) => {
try { try {
const doc = await (tx || db)(TableName.DynamicSecretLease) const doc = await (tx || db.replicaNode())(TableName.DynamicSecretLease)
.where({ [`${TableName.DynamicSecretLease}.id` as "id"]: id }) .where({ [`${TableName.DynamicSecretLease}.id` as "id"]: id })
.first() .first()
.join( .join(

View File

@ -12,7 +12,7 @@ export const groupDALFactory = (db: TDbClient) => {
const findGroups = async (filter: TFindFilter<TGroups>, { offset, limit, sort, tx }: TFindOpt<TGroups> = {}) => { const findGroups = async (filter: TFindFilter<TGroups>, { offset, limit, sort, tx }: TFindOpt<TGroups> = {}) => {
try { try {
const query = (tx || db)(TableName.Groups) const query = (tx || db.replicaNode())(TableName.Groups)
// eslint-disable-next-line // eslint-disable-next-line
.where(buildFindFilter(filter)) .where(buildFindFilter(filter))
.select(selectAllTableCols(TableName.Groups)); .select(selectAllTableCols(TableName.Groups));
@ -32,7 +32,7 @@ export const groupDALFactory = (db: TDbClient) => {
const findByOrgId = async (orgId: string, tx?: Knex) => { const findByOrgId = async (orgId: string, tx?: Knex) => {
try { try {
const docs = await (tx || db)(TableName.Groups) const docs = await (tx || db.replicaNode())(TableName.Groups)
.where(`${TableName.Groups}.orgId`, orgId) .where(`${TableName.Groups}.orgId`, orgId)
.leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`) .leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`)
.select(selectAllTableCols(TableName.Groups)) .select(selectAllTableCols(TableName.Groups))
@ -74,11 +74,12 @@ export const groupDALFactory = (db: TDbClient) => {
username?: string; username?: string;
}) => { }) => {
try { try {
let query = db(TableName.OrgMembership) let query = db
.replicaNode()(TableName.OrgMembership)
.where(`${TableName.OrgMembership}.orgId`, orgId) .where(`${TableName.OrgMembership}.orgId`, orgId)
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.UserGroupMembership, function () { .leftJoin(TableName.UserGroupMembership, (bd) => {
this.on(`${TableName.UserGroupMembership}.userId`, "=", `${TableName.Users}.id`).andOn( bd.on(`${TableName.UserGroupMembership}.userId`, "=", `${TableName.Users}.id`).andOn(
`${TableName.UserGroupMembership}.groupId`, `${TableName.UserGroupMembership}.groupId`,
"=", "=",
db.raw("?", [groupId]) db.raw("?", [groupId])

View File

@ -18,7 +18,7 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
*/ */
const filterProjectsByUserMembership = async (userId: string, groupId: string, projectIds: string[], tx?: Knex) => { const filterProjectsByUserMembership = async (userId: string, groupId: string, projectIds: string[], tx?: Knex) => {
try { try {
const userProjectMemberships: string[] = await (tx || db)(TableName.ProjectMembership) const userProjectMemberships: string[] = await (tx || db.replicaNode())(TableName.ProjectMembership)
.where(`${TableName.ProjectMembership}.userId`, userId) .where(`${TableName.ProjectMembership}.userId`, userId)
.whereIn(`${TableName.ProjectMembership}.projectId`, projectIds) .whereIn(`${TableName.ProjectMembership}.projectId`, projectIds)
.pluck(`${TableName.ProjectMembership}.projectId`); .pluck(`${TableName.ProjectMembership}.projectId`);
@ -43,7 +43,8 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
// special query // special query
const findUserGroupMembershipsInProject = async (usernames: string[], projectId: string) => { const findUserGroupMembershipsInProject = async (usernames: string[], projectId: string) => {
try { try {
const usernameDocs: string[] = await db(TableName.UserGroupMembership) const usernameDocs: string[] = await db
.replicaNode()(TableName.UserGroupMembership)
.join( .join(
TableName.GroupProjectMembership, TableName.GroupProjectMembership,
`${TableName.UserGroupMembership}.groupId`, `${TableName.UserGroupMembership}.groupId`,
@ -73,7 +74,7 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
try { try {
// get list of groups in the project with id [projectId] // get list of groups in the project with id [projectId]
// that that are not the group with id [groupId] // that that are not the group with id [groupId]
const groups: string[] = await (tx || db)(TableName.GroupProjectMembership) const groups: string[] = await (tx || db.replicaNode())(TableName.GroupProjectMembership)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId) .where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.whereNot(`${TableName.GroupProjectMembership}.groupId`, groupId) .whereNot(`${TableName.GroupProjectMembership}.groupId`, groupId)
.pluck(`${TableName.GroupProjectMembership}.groupId`); .pluck(`${TableName.GroupProjectMembership}.groupId`);
@ -83,8 +84,8 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
.where(`${TableName.UserGroupMembership}.groupId`, groupId) .where(`${TableName.UserGroupMembership}.groupId`, groupId)
.where(`${TableName.UserGroupMembership}.isPending`, false) .where(`${TableName.UserGroupMembership}.isPending`, false)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.ProjectMembership, function () { .leftJoin(TableName.ProjectMembership, (bd) => {
this.on(`${TableName.Users}.id`, "=", `${TableName.ProjectMembership}.userId`).andOn( bd.on(`${TableName.Users}.id`, "=", `${TableName.ProjectMembership}.userId`).andOn(
`${TableName.ProjectMembership}.projectId`, `${TableName.ProjectMembership}.projectId`,
"=", "=",
db.raw("?", [projectId]) db.raw("?", [projectId])
@ -107,9 +108,9 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
db.ref("publicKey").withSchema(TableName.UserEncryptionKey) db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
) )
.where({ isGhost: false }) // MAKE SURE USER IS NOT A GHOST USER .where({ isGhost: false }) // MAKE SURE USER IS NOT A GHOST USER
.whereNotIn(`${TableName.UserGroupMembership}.userId`, function () { .whereNotIn(`${TableName.UserGroupMembership}.userId`, (bd) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.select(`${TableName.UserGroupMembership}.userId`) bd.select(`${TableName.UserGroupMembership}.userId`)
.from(TableName.UserGroupMembership) .from(TableName.UserGroupMembership)
.whereIn(`${TableName.UserGroupMembership}.groupId`, groups); .whereIn(`${TableName.UserGroupMembership}.groupId`, groups);
}); });

View File

@ -23,6 +23,8 @@ import {
} from "@app/lib/crypto/encryption"; } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal";
@ -30,6 +32,7 @@ import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membe
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal"; import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
import { normalizeUsername } from "@app/services/user/user-fns"; import { normalizeUsername } from "@app/services/user/user-fns";
@ -50,7 +53,7 @@ import {
TTestLdapConnectionDTO, TTestLdapConnectionDTO,
TUpdateLdapCfgDTO TUpdateLdapCfgDTO
} from "./ldap-config-types"; } from "./ldap-config-types";
import { testLDAPConfig } from "./ldap-fns"; import { searchGroups, testLDAPConfig } from "./ldap-fns";
import { TLdapGroupMapDALFactory } from "./ldap-group-map-dal"; import { TLdapGroupMapDALFactory } from "./ldap-group-map-dal";
type TLdapConfigServiceFactoryDep = { type TLdapConfigServiceFactoryDep = {
@ -84,6 +87,8 @@ type TLdapConfigServiceFactoryDep = {
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">; userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">; licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
smtpService: Pick<TSmtpService, "sendMail">;
}; };
export type TLdapConfigServiceFactory = ReturnType<typeof ldapConfigServiceFactory>; export type TLdapConfigServiceFactory = ReturnType<typeof ldapConfigServiceFactory>;
@ -103,7 +108,9 @@ export const ldapConfigServiceFactory = ({
userDAL, userDAL,
userAliasDAL, userAliasDAL,
permissionService, permissionService,
licenseService licenseService,
tokenService,
smtpService
}: TLdapConfigServiceFactoryDep) => { }: TLdapConfigServiceFactoryDep) => {
const createLdapCfg = async ({ const createLdapCfg = async ({
actor, actor,
@ -115,6 +122,7 @@ export const ldapConfigServiceFactory = ({
url, url,
bindDN, bindDN,
bindPass, bindPass,
uniqueUserAttribute,
searchBase, searchBase,
searchFilter, searchFilter,
groupSearchBase, groupSearchBase,
@ -193,6 +201,7 @@ export const ldapConfigServiceFactory = ({
encryptedBindPass, encryptedBindPass,
bindPassIV, bindPassIV,
bindPassTag, bindPassTag,
uniqueUserAttribute,
searchBase, searchBase,
searchFilter, searchFilter,
groupSearchBase, groupSearchBase,
@ -215,6 +224,7 @@ export const ldapConfigServiceFactory = ({
url, url,
bindDN, bindDN,
bindPass, bindPass,
uniqueUserAttribute,
searchBase, searchBase,
searchFilter, searchFilter,
groupSearchBase, groupSearchBase,
@ -237,7 +247,8 @@ export const ldapConfigServiceFactory = ({
searchBase, searchBase,
searchFilter, searchFilter,
groupSearchBase, groupSearchBase,
groupSearchFilter groupSearchFilter,
uniqueUserAttribute
}; };
const orgBot = await orgBotDAL.findOne({ orgId }); const orgBot = await orgBotDAL.findOne({ orgId });
@ -275,7 +286,7 @@ export const ldapConfigServiceFactory = ({
return ldapConfig; return ldapConfig;
}; };
const getLdapCfg = async (filter: { orgId: string; isActive?: boolean }) => { const getLdapCfg = async (filter: { orgId: string; isActive?: boolean; id?: string }) => {
const ldapConfig = await ldapConfigDAL.findOne(filter); const ldapConfig = await ldapConfigDAL.findOne(filter);
if (!ldapConfig) throw new BadRequestError({ message: "Failed to find organization LDAP data" }); if (!ldapConfig) throw new BadRequestError({ message: "Failed to find organization LDAP data" });
@ -338,6 +349,7 @@ export const ldapConfigServiceFactory = ({
url: ldapConfig.url, url: ldapConfig.url,
bindDN, bindDN,
bindPass, bindPass,
uniqueUserAttribute: ldapConfig.uniqueUserAttribute,
searchBase: ldapConfig.searchBase, searchBase: ldapConfig.searchBase,
searchFilter: ldapConfig.searchFilter, searchFilter: ldapConfig.searchFilter,
groupSearchBase: ldapConfig.groupSearchBase, groupSearchBase: ldapConfig.groupSearchBase,
@ -374,6 +386,7 @@ export const ldapConfigServiceFactory = ({
url: ldapConfig.url, url: ldapConfig.url,
bindDN: ldapConfig.bindDN, bindDN: ldapConfig.bindDN,
bindCredentials: ldapConfig.bindPass, bindCredentials: ldapConfig.bindPass,
uniqueUserAttribute: ldapConfig.uniqueUserAttribute,
searchBase: ldapConfig.searchBase, searchBase: ldapConfig.searchBase,
searchFilter: ldapConfig.searchFilter || "(uid={{username}})", searchFilter: ldapConfig.searchFilter || "(uid={{username}})",
// searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"], // searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"],
@ -443,6 +456,21 @@ export const ldapConfigServiceFactory = ({
} }
}); });
} else { } else {
const plan = await licenseService.getPlan(orgId);
if (plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
throw new BadRequestError({
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
});
}
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
throw new BadRequestError({
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
});
}
userAlias = await userDAL.transaction(async (tx) => { userAlias = await userDAL.transaction(async (tx) => {
let newUser: TUsers | undefined; let newUser: TUsers | undefined;
if (serverCfg.trustSamlEmails) { if (serverCfg.trustSamlEmails) {
@ -494,7 +522,7 @@ export const ldapConfigServiceFactory = ({
if (!orgMembership) { if (!orgMembership) {
await orgMembershipDAL.create( await orgMembershipDAL.create(
{ {
userId: userAlias.userId, userId: newUser.id,
inviteEmail: email, inviteEmail: email,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
@ -627,6 +655,22 @@ export const ldapConfigServiceFactory = ({
} }
); );
if (user.email && !user.isEmailVerified) {
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_VERIFICATION,
userId: user.id
});
await smtpService.sendMail({
template: SmtpTemplates.EmailVerification,
subjectLine: "Infisical confirmation code",
recipients: [user.email],
substitutions: {
code: token
}
});
}
return { isUserCompleted, providerAuthToken }; return { isUserCompleted, providerAuthToken };
}; };
@ -672,11 +716,25 @@ export const ldapConfigServiceFactory = ({
message: "Failed to create LDAP group map due to plan restriction. Upgrade plan to create LDAP group map." message: "Failed to create LDAP group map due to plan restriction. Upgrade plan to create LDAP group map."
}); });
const ldapConfig = await ldapConfigDAL.findOne({ const ldapConfig = await getLdapCfg({
id: ldapConfigId, orgId,
orgId id: ldapConfigId
}); });
if (!ldapConfig) throw new BadRequestError({ message: "Failed to find organization LDAP data" });
if (!ldapConfig.groupSearchBase) {
throw new BadRequestError({
message: "Configure a group search base in your LDAP configuration in order to proceed."
});
}
const groupSearchFilter = `(cn=${ldapGroupCN})`;
const groups = await searchGroups(ldapConfig, groupSearchFilter, ldapConfig.groupSearchBase);
if (!groups.some((g) => g.cn === ldapGroupCN)) {
throw new BadRequestError({
message: "Failed to find LDAP Group CN"
});
}
const group = await groupDAL.findOne({ slug: groupSlug, orgId }); const group = await groupDAL.findOne({ slug: groupSlug, orgId });
if (!group) throw new BadRequestError({ message: "Failed to find group" }); if (!group) throw new BadRequestError({ message: "Failed to find group" });

View File

@ -7,6 +7,7 @@ export type TLDAPConfig = {
url: string; url: string;
bindDN: string; bindDN: string;
bindPass: string; bindPass: string;
uniqueUserAttribute: string;
searchBase: string; searchBase: string;
groupSearchBase: string; groupSearchBase: string;
groupSearchFilter: string; groupSearchFilter: string;
@ -19,6 +20,7 @@ export type TCreateLdapCfgDTO = {
url: string; url: string;
bindDN: string; bindDN: string;
bindPass: string; bindPass: string;
uniqueUserAttribute: string;
searchBase: string; searchBase: string;
searchFilter: string; searchFilter: string;
groupSearchBase: string; groupSearchBase: string;
@ -33,6 +35,7 @@ export type TUpdateLdapCfgDTO = {
url: string; url: string;
bindDN: string; bindDN: string;
bindPass: string; bindPass: string;
uniqueUserAttribute: string;
searchBase: string; searchBase: string;
searchFilter: string; searchFilter: string;
groupSearchBase: string; groupSearchBase: string;

View File

@ -10,7 +10,8 @@ export const ldapGroupMapDALFactory = (db: TDbClient) => {
const findLdapGroupMapsByLdapConfigId = async (ldapConfigId: string) => { const findLdapGroupMapsByLdapConfigId = async (ldapConfigId: string) => {
try { try {
const docs = await db(TableName.LdapGroupMap) const docs = await db
.replicaNode()(TableName.LdapGroupMap)
.where(`${TableName.LdapGroupMap}.ldapConfigId`, ldapConfigId) .where(`${TableName.LdapGroupMap}.ldapConfigId`, ldapConfigId)
.join(TableName.Groups, `${TableName.LdapGroupMap}.groupId`, `${TableName.Groups}.id`) .join(TableName.Groups, `${TableName.LdapGroupMap}.groupId`, `${TableName.Groups}.id`)
.select(selectAllTableCols(TableName.LdapGroupMap)) .select(selectAllTableCols(TableName.LdapGroupMap))

View File

@ -7,6 +7,8 @@ export const getDefaultOnPremFeatures = () => {
workspacesUsed: 0, workspacesUsed: 0,
memberLimit: null, memberLimit: null,
membersUsed: 0, membersUsed: 0,
identityLimit: null,
identitiesUsed: 0,
environmentLimit: null, environmentLimit: null,
environmentsUsed: 0, environmentsUsed: 0,
secretVersioning: true, secretVersioning: true,

View File

@ -15,6 +15,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
membersUsed: 0, membersUsed: 0,
environmentLimit: null, environmentLimit: null,
environmentsUsed: 0, environmentsUsed: 0,
identityLimit: null,
identitiesUsed: 0,
dynamicSecret: false, dynamicSecret: false,
secretVersioning: true, secretVersioning: true,
pitRecovery: false, pitRecovery: false,
@ -27,6 +29,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
auditLogStreams: false, auditLogStreams: false,
auditLogStreamLimit: 3, auditLogStreamLimit: 3,
samlSSO: false, samlSSO: false,
oidcSSO: false,
scim: false, scim: false,
ldap: false, ldap: false,
groups: false, groups: false,

View File

@ -9,7 +9,7 @@ export type TLicenseDALFactory = ReturnType<typeof licenseDALFactory>;
export const licenseDALFactory = (db: TDbClient) => { export const licenseDALFactory = (db: TDbClient) => {
const countOfOrgMembers = async (orgId: string | null, tx?: Knex) => { const countOfOrgMembers = async (orgId: string | null, tx?: Knex) => {
try { try {
const doc = await (tx || db)(TableName.OrgMembership) const doc = await (tx || db.replicaNode())(TableName.OrgMembership)
.where({ status: OrgMembershipStatus.Accepted }) .where({ status: OrgMembershipStatus.Accepted })
.andWhere((bd) => { .andWhere((bd) => {
if (orgId) { if (orgId) {
@ -19,11 +19,44 @@ export const licenseDALFactory = (db: TDbClient) => {
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false) .where(`${TableName.Users}.isGhost`, false)
.count(); .count();
return doc?.[0].count; return Number(doc?.[0].count);
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Count of Org Members" }); throw new DatabaseError({ error, name: "Count of Org Members" });
} }
}; };
return { countOfOrgMembers }; const countOrgUsersAndIdentities = async (orgId: string | null, tx?: Knex) => {
try {
// count org users
const userDoc = await (tx || db)(TableName.OrgMembership)
.where({ status: OrgMembershipStatus.Accepted })
.andWhere((bd) => {
if (orgId) {
void bd.where({ orgId });
}
})
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false)
.count();
const userCount = Number(userDoc?.[0].count);
// count org identities
const identityDoc = await (tx || db)(TableName.IdentityOrgMembership)
.where((bd) => {
if (orgId) {
void bd.where({ orgId });
}
})
.count();
const identityCount = Number(identityDoc?.[0].count);
return userCount + identityCount;
} catch (error) {
throw new DatabaseError({ error, name: "Count of Org Users + Identities" });
}
};
return { countOfOrgMembers, countOrgUsersAndIdentities };
}; };

View File

@ -155,6 +155,7 @@ export const licenseServiceFactory = ({
LICENSE_SERVER_CLOUD_PLAN_TTL, LICENSE_SERVER_CLOUD_PLAN_TTL,
JSON.stringify(currentPlan) JSON.stringify(currentPlan)
); );
return currentPlan; return currentPlan;
} }
} catch (error) { } catch (error) {
@ -204,16 +205,22 @@ export const licenseServiceFactory = ({
const org = await orgDAL.findOrgById(orgId); const org = await orgDAL.findOrgById(orgId);
if (!org) throw new BadRequestError({ message: "Org not found" }); if (!org) throw new BadRequestError({ message: "Org not found" });
const count = await licenseDAL.countOfOrgMembers(orgId); const quantity = await licenseDAL.countOfOrgMembers(orgId);
const quantityIdentities = await licenseDAL.countOrgUsersAndIdentities(orgId);
if (org?.customerId) { if (org?.customerId) {
await licenseServerCloudApi.request.patch(`/api/license-server/v1/customers/${org.customerId}/cloud-plan`, { await licenseServerCloudApi.request.patch(`/api/license-server/v1/customers/${org.customerId}/cloud-plan`, {
quantity: count quantity,
quantityIdentities
}); });
} }
await keyStore.deleteItem(FEATURE_CACHE_KEY(orgId)); await keyStore.deleteItem(FEATURE_CACHE_KEY(orgId));
} else if (instanceType === InstanceType.EnterpriseOnPrem) { } else if (instanceType === InstanceType.EnterpriseOnPrem) {
const usedSeats = await licenseDAL.countOfOrgMembers(null); const usedSeats = await licenseDAL.countOfOrgMembers(null);
await licenseServerOnPremApi.request.patch(`/api/license/v1/license`, { usedSeats }); const usedIdentitySeats = await licenseDAL.countOrgUsersAndIdentities(null);
await licenseServerOnPremApi.request.patch(`/api/license/v1/license`, {
usedSeats,
usedIdentitySeats
});
} }
await refreshPlan(orgId); await refreshPlan(orgId);
}; };

View File

@ -31,6 +31,8 @@ export type TFeatureSet = {
dynamicSecret: false; dynamicSecret: false;
memberLimit: null; memberLimit: null;
membersUsed: 0; membersUsed: 0;
identityLimit: null;
identitiesUsed: 0;
environmentLimit: null; environmentLimit: null;
environmentsUsed: 0; environmentsUsed: 0;
secretVersioning: true; secretVersioning: true;
@ -44,6 +46,7 @@ export type TFeatureSet = {
auditLogStreams: false; auditLogStreams: false;
auditLogStreamLimit: 3; auditLogStreamLimit: 3;
samlSSO: false; samlSSO: false;
oidcSSO: false;
scim: false; scim: false;
ldap: false; ldap: false;
groups: false; groups: false;

View File

@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TOidcConfigDALFactory = ReturnType<typeof oidcConfigDALFactory>;
export const oidcConfigDALFactory = (db: TDbClient) => {
const oidcCfgOrm = ormify(db, TableName.OidcConfig);
return { ...oidcCfgOrm };
};

View File

@ -0,0 +1,637 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
import { OrgMembershipRole, OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { getConfig } from "@app/lib/config/env";
import {
decryptSymmetric,
encryptSymmetric,
generateAsymmetricKeyPair,
generateSymmetricKey,
infisicalSymmetricDecrypt,
infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { normalizeUsername } from "@app/services/user/user-fns";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TOidcConfigDALFactory } from "./oidc-config-dal";
import {
OIDCConfigurationType,
TCreateOidcCfgDTO,
TGetOidcCfgDTO,
TOidcLoginDTO,
TUpdateOidcCfgDTO
} from "./oidc-config-types";
type TOidcConfigServiceFactoryDep = {
userDAL: Pick<
TUserDALFactory,
"create" | "findOne" | "transaction" | "updateById" | "findById" | "findUserEncKeyByUserId"
>;
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
orgDAL: Pick<
TOrgDALFactory,
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
>;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
smtpService: Pick<TSmtpService, "sendMail">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne" | "update" | "create">;
};
export type TOidcConfigServiceFactory = ReturnType<typeof oidcConfigServiceFactory>;
export const oidcConfigServiceFactory = ({
orgDAL,
orgMembershipDAL,
userDAL,
userAliasDAL,
licenseService,
permissionService,
tokenService,
orgBotDAL,
smtpService,
oidcConfigDAL
}: TOidcConfigServiceFactoryDep) => {
const getOidc = async (dto: TGetOidcCfgDTO) => {
const org = await orgDAL.findOne({ slug: dto.orgSlug });
if (!org) {
throw new BadRequestError({
message: "Organization not found",
name: "OrgNotFound"
});
}
if (dto.type === "external") {
const { permission } = await permissionService.getOrgPermission(
dto.actor,
dto.actorId,
org.id,
dto.actorAuthMethod,
dto.actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
}
const oidcCfg = await oidcConfigDAL.findOne({
orgId: org.id
});
if (!oidcCfg) {
throw new BadRequestError({
message: "Failed to find organization OIDC configuration"
});
}
// decrypt and return cfg
const orgBot = await orgBotDAL.findOne({ orgId: oidcCfg.orgId });
if (!orgBot) {
throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
}
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
tag: orgBot.symmetricKeyTag,
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
});
const { encryptedClientId, clientIdIV, clientIdTag, encryptedClientSecret, clientSecretIV, clientSecretTag } =
oidcCfg;
let clientId = "";
if (encryptedClientId && clientIdIV && clientIdTag) {
clientId = decryptSymmetric({
ciphertext: encryptedClientId,
key,
tag: clientIdTag,
iv: clientIdIV
});
}
let clientSecret = "";
if (encryptedClientSecret && clientSecretIV && clientSecretTag) {
clientSecret = decryptSymmetric({
key,
tag: clientSecretTag,
iv: clientSecretIV,
ciphertext: encryptedClientSecret
});
}
return {
id: oidcCfg.id,
issuer: oidcCfg.issuer,
authorizationEndpoint: oidcCfg.authorizationEndpoint,
configurationType: oidcCfg.configurationType,
discoveryURL: oidcCfg.discoveryURL,
jwksUri: oidcCfg.jwksUri,
tokenEndpoint: oidcCfg.tokenEndpoint,
userinfoEndpoint: oidcCfg.userinfoEndpoint,
orgId: oidcCfg.orgId,
isActive: oidcCfg.isActive,
allowedEmailDomains: oidcCfg.allowedEmailDomains,
clientId,
clientSecret
};
};
const oidcLogin = async ({ externalId, email, firstName, lastName, orgId, callbackPort }: TOidcLoginDTO) => {
const serverCfg = await getServerCfg();
const appCfg = getConfig();
const userAlias = await userAliasDAL.findOne({
externalId,
orgId,
aliasType: UserAliasType.OIDC
});
const organization = await orgDAL.findOrgById(orgId);
if (!organization) throw new BadRequestError({ message: "Org not found" });
let user: TUsers;
if (userAlias) {
user = await userDAL.transaction(async (tx) => {
const foundUser = await userDAL.findById(userAlias.userId, tx);
const [orgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: foundUser.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
},
{ tx }
);
if (!orgMembership) {
await orgMembershipDAL.create(
{
userId: userAlias.userId,
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
},
tx
);
// Only update the membership to Accepted if the user account is already completed.
} else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
await orgDAL.updateMembershipById(
orgMembership.id,
{
status: OrgMembershipStatus.Accepted
},
tx
);
}
return foundUser;
});
} else {
user = await userDAL.transaction(async (tx) => {
let newUser: TUsers | undefined;
if (serverCfg.trustOidcEmails) {
newUser = await userDAL.findOne(
{
email,
isEmailVerified: true
},
tx
);
}
if (!newUser) {
const uniqueUsername = await normalizeUsername(externalId, userDAL);
newUser = await userDAL.create(
{
email,
firstName,
isEmailVerified: serverCfg.trustOidcEmails,
username: serverCfg.trustOidcEmails ? email : uniqueUsername,
lastName,
authMethods: [],
isGhost: false
},
tx
);
}
await userAliasDAL.create(
{
userId: newUser.id,
aliasType: UserAliasType.OIDC,
externalId,
emails: email ? [email] : [],
orgId
},
tx
);
const [orgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
},
{ tx }
);
if (!orgMembership) {
await orgMembershipDAL.create(
{
userId: newUser.id,
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
},
tx
);
// Only update the membership to Accepted if the user account is already completed.
} else if (orgMembership.status === OrgMembershipStatus.Invited && newUser.isAccepted) {
await orgDAL.updateMembershipById(
orgMembership.id,
{
status: OrgMembershipStatus.Accepted
},
tx
);
}
return newUser;
});
}
await licenseService.updateSubscriptionOrgMemberCount(organization.id);
const userEnc = await userDAL.findUserEncKeyByUserId(user.id);
const isUserCompleted = Boolean(user.isAccepted);
const providerAuthToken = jwt.sign(
{
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id,
username: user.username,
...(user.email && { email: user.email, isEmailVerified: user.isEmailVerified }),
firstName,
lastName,
organizationName: organization.name,
organizationId: organization.id,
organizationSlug: organization.slug,
hasExchangedPrivateKey: Boolean(userEnc?.serverEncryptedPrivateKey),
authMethod: AuthMethod.OIDC,
authType: UserAliasType.OIDC,
isUserCompleted,
...(callbackPort && { callbackPort })
},
appCfg.AUTH_SECRET,
{
expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME
}
);
if (user.email && !user.isEmailVerified) {
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_VERIFICATION,
userId: user.id
});
await smtpService.sendMail({
template: SmtpTemplates.EmailVerification,
subjectLine: "Infisical confirmation code",
recipients: [user.email],
substitutions: {
code: token
}
});
}
return { isUserCompleted, providerAuthToken };
};
const updateOidcCfg = async ({
orgSlug,
allowedEmailDomains,
configurationType,
discoveryURL,
actor,
actorOrgId,
actorAuthMethod,
actorId,
issuer,
isActive,
authorizationEndpoint,
jwksUri,
tokenEndpoint,
userinfoEndpoint,
clientId,
clientSecret
}: TUpdateOidcCfgDTO) => {
const org = await orgDAL.findOne({
slug: orgSlug
});
if (!org) {
throw new BadRequestError({
message: "Organization not found"
});
}
const plan = await licenseService.getPlan(org.id);
if (!plan.oidcSSO)
throw new BadRequestError({
message:
"Failed to update OIDC SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
org.id,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
const orgBot = await orgBotDAL.findOne({ orgId: org.id });
if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
tag: orgBot.symmetricKeyTag,
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
});
const updateQuery: TOidcConfigsUpdate = {
allowedEmailDomains,
configurationType,
discoveryURL,
issuer,
authorizationEndpoint,
tokenEndpoint,
userinfoEndpoint,
jwksUri,
isActive
};
if (clientId !== undefined) {
const { ciphertext: encryptedClientId, iv: clientIdIV, tag: clientIdTag } = encryptSymmetric(clientId, key);
updateQuery.encryptedClientId = encryptedClientId;
updateQuery.clientIdIV = clientIdIV;
updateQuery.clientIdTag = clientIdTag;
}
if (clientSecret !== undefined) {
const {
ciphertext: encryptedClientSecret,
iv: clientSecretIV,
tag: clientSecretTag
} = encryptSymmetric(clientSecret, key);
updateQuery.encryptedClientSecret = encryptedClientSecret;
updateQuery.clientSecretIV = clientSecretIV;
updateQuery.clientSecretTag = clientSecretTag;
}
const [ssoConfig] = await oidcConfigDAL.update({ orgId: org.id }, updateQuery);
return ssoConfig;
};
const createOidcCfg = async ({
orgSlug,
allowedEmailDomains,
configurationType,
discoveryURL,
actor,
actorOrgId,
actorAuthMethod,
actorId,
issuer,
isActive,
authorizationEndpoint,
jwksUri,
tokenEndpoint,
userinfoEndpoint,
clientId,
clientSecret
}: TCreateOidcCfgDTO) => {
const org = await orgDAL.findOne({
slug: orgSlug
});
if (!org) {
throw new BadRequestError({
message: "Organization not found"
});
}
const plan = await licenseService.getPlan(org.id);
if (!plan.oidcSSO)
throw new BadRequestError({
message:
"Failed to create OIDC SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
org.id,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
const orgBot = await orgBotDAL.transaction(async (tx) => {
const doc = await orgBotDAL.findOne({ orgId: org.id }, tx);
if (doc) return doc;
const { privateKey, publicKey } = generateAsymmetricKeyPair();
const key = generateSymmetricKey();
const {
ciphertext: encryptedPrivateKey,
iv: privateKeyIV,
tag: privateKeyTag,
encoding: privateKeyKeyEncoding,
algorithm: privateKeyAlgorithm
} = infisicalSymmetricEncypt(privateKey);
const {
ciphertext: encryptedSymmetricKey,
iv: symmetricKeyIV,
tag: symmetricKeyTag,
encoding: symmetricKeyKeyEncoding,
algorithm: symmetricKeyAlgorithm
} = infisicalSymmetricEncypt(key);
return orgBotDAL.create(
{
name: "Infisical org bot",
publicKey,
privateKeyIV,
encryptedPrivateKey,
symmetricKeyIV,
symmetricKeyTag,
encryptedSymmetricKey,
symmetricKeyAlgorithm,
orgId: org.id,
privateKeyTag,
privateKeyAlgorithm,
privateKeyKeyEncoding,
symmetricKeyKeyEncoding
},
tx
);
});
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
tag: orgBot.symmetricKeyTag,
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
});
const { ciphertext: encryptedClientId, iv: clientIdIV, tag: clientIdTag } = encryptSymmetric(clientId, key);
const {
ciphertext: encryptedClientSecret,
iv: clientSecretIV,
tag: clientSecretTag
} = encryptSymmetric(clientSecret, key);
const oidcCfg = await oidcConfigDAL.create({
issuer,
isActive,
configurationType,
discoveryURL,
authorizationEndpoint,
allowedEmailDomains,
jwksUri,
tokenEndpoint,
userinfoEndpoint,
orgId: org.id,
encryptedClientId,
clientIdIV,
clientIdTag,
encryptedClientSecret,
clientSecretIV,
clientSecretTag
});
return oidcCfg;
};
const getOrgAuthStrategy = async (orgSlug: string, callbackPort?: string) => {
const appCfg = getConfig();
const org = await orgDAL.findOne({
slug: orgSlug
});
if (!org) {
throw new BadRequestError({
message: "Organization not found."
});
}
const oidcCfg = await getOidc({
type: "internal",
orgSlug
});
if (!oidcCfg || !oidcCfg.isActive) {
throw new BadRequestError({
message: "Failed to authenticate with OIDC SSO"
});
}
let issuer: Issuer;
if (oidcCfg.configurationType === OIDCConfigurationType.DISCOVERY_URL) {
if (!oidcCfg.discoveryURL) {
throw new BadRequestError({
message: "OIDC not configured correctly"
});
}
issuer = await Issuer.discover(oidcCfg.discoveryURL);
} else {
if (
!oidcCfg.issuer ||
!oidcCfg.authorizationEndpoint ||
!oidcCfg.jwksUri ||
!oidcCfg.tokenEndpoint ||
!oidcCfg.userinfoEndpoint
) {
throw new BadRequestError({
message: "OIDC not configured correctly"
});
}
issuer = new OpenIdIssuer({
issuer: oidcCfg.issuer,
authorization_endpoint: oidcCfg.authorizationEndpoint,
jwks_uri: oidcCfg.jwksUri,
token_endpoint: oidcCfg.tokenEndpoint,
userinfo_endpoint: oidcCfg.userinfoEndpoint
});
}
const client = new issuer.Client({
client_id: oidcCfg.clientId,
client_secret: oidcCfg.clientSecret,
redirect_uris: [`${appCfg.SITE_URL}/api/v1/sso/oidc/callback`]
});
const strategy = new OpenIdStrategy(
{
client,
passReqToCallback: true
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(_req: any, tokenSet: TokenSet, cb: any) => {
const claims = tokenSet.claims();
if (!claims.email || !claims.given_name) {
throw new BadRequestError({
message: "Invalid request. Missing email or first name"
});
}
if (oidcCfg.allowedEmailDomains) {
const allowedDomains = oidcCfg.allowedEmailDomains.split(", ");
if (!allowedDomains.includes(claims.email.split("@")[1])) {
throw new BadRequestError({
message: "Email not allowed."
});
}
}
oidcLogin({
email: claims.email,
externalId: claims.sub,
firstName: claims.given_name ?? "",
lastName: claims.family_name ?? "",
orgId: org.id,
callbackPort
})
.then(({ isUserCompleted, providerAuthToken }) => {
cb(null, { isUserCompleted, providerAuthToken });
})
.catch((error) => {
cb(error);
});
}
);
return strategy;
};
return { oidcLogin, getOrgAuthStrategy, getOidc, updateOidcCfg, createOidcCfg };
};

View File

@ -0,0 +1,56 @@
import { TGenericPermission } from "@app/lib/types";
export enum OIDCConfigurationType {
CUSTOM = "custom",
DISCOVERY_URL = "discoveryURL"
}
export type TOidcLoginDTO = {
externalId: string;
email: string;
firstName: string;
lastName?: string;
orgId: string;
callbackPort?: string;
};
export type TGetOidcCfgDTO =
| ({
type: "external";
orgSlug: string;
} & TGenericPermission)
| {
type: "internal";
orgSlug: string;
};
export type TCreateOidcCfgDTO = {
issuer?: string;
authorizationEndpoint?: string;
discoveryURL?: string;
configurationType: OIDCConfigurationType;
allowedEmailDomains?: string;
jwksUri?: string;
tokenEndpoint?: string;
userinfoEndpoint?: string;
clientId: string;
clientSecret: string;
isActive: boolean;
orgSlug: string;
} & TGenericPermission;
export type TUpdateOidcCfgDTO = Partial<{
issuer: string;
authorizationEndpoint: string;
allowedEmailDomains: string;
discoveryURL: string;
jwksUri: string;
configurationType: OIDCConfigurationType;
tokenEndpoint: string;
userinfoEndpoint: string;
clientId: string;
clientSecret: string;
isActive: boolean;
orgSlug: string;
}> &
TGenericPermission;

View File

@ -116,7 +116,6 @@ const buildMemberPermission = () => {
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role); can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount); can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);

View File

@ -10,7 +10,8 @@ export type TPermissionDALFactory = ReturnType<typeof permissionDALFactory>;
export const permissionDALFactory = (db: TDbClient) => { export const permissionDALFactory = (db: TDbClient) => {
const getOrgPermission = async (userId: string, orgId: string) => { const getOrgPermission = async (userId: string, orgId: string) => {
try { try {
const membership = await db(TableName.OrgMembership) const membership = await db
.replicaNode()(TableName.OrgMembership)
.leftJoin(TableName.OrgRoles, `${TableName.OrgMembership}.roleId`, `${TableName.OrgRoles}.id`) .leftJoin(TableName.OrgRoles, `${TableName.OrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
.join(TableName.Organization, `${TableName.OrgMembership}.orgId`, `${TableName.Organization}.id`) .join(TableName.Organization, `${TableName.OrgMembership}.orgId`, `${TableName.Organization}.id`)
.where("userId", userId) .where("userId", userId)
@ -28,7 +29,8 @@ export const permissionDALFactory = (db: TDbClient) => {
const getOrgIdentityPermission = async (identityId: string, orgId: string) => { const getOrgIdentityPermission = async (identityId: string, orgId: string) => {
try { try {
const membership = await db(TableName.IdentityOrgMembership) const membership = await db
.replicaNode()(TableName.IdentityOrgMembership)
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`) .leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
.join(TableName.Organization, `${TableName.IdentityOrgMembership}.orgId`, `${TableName.Organization}.id`) .join(TableName.Organization, `${TableName.IdentityOrgMembership}.orgId`, `${TableName.Organization}.id`)
.where("identityId", identityId) .where("identityId", identityId)
@ -45,11 +47,13 @@ export const permissionDALFactory = (db: TDbClient) => {
const getProjectPermission = async (userId: string, projectId: string) => { const getProjectPermission = async (userId: string, projectId: string) => {
try { try {
const groups: string[] = await db(TableName.GroupProjectMembership) const groups: string[] = await db
.replicaNode()(TableName.GroupProjectMembership)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId) .where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.pluck(`${TableName.GroupProjectMembership}.groupId`); .pluck(`${TableName.GroupProjectMembership}.groupId`);
const groupDocs = await db(TableName.UserGroupMembership) const groupDocs = await db
.replicaNode()(TableName.UserGroupMembership)
.where(`${TableName.UserGroupMembership}.userId`, userId) .where(`${TableName.UserGroupMembership}.userId`, userId)
.whereIn(`${TableName.UserGroupMembership}.groupId`, groups) .whereIn(`${TableName.UserGroupMembership}.groupId`, groups)
.join( .join(
@ -231,7 +235,8 @@ export const permissionDALFactory = (db: TDbClient) => {
const getProjectIdentityPermission = async (identityId: string, projectId: string) => { const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
try { try {
const docs = await db(TableName.IdentityProjectMembership) const docs = await db
.replicaNode()(TableName.IdentityProjectMembership)
.join( .join(
TableName.IdentityProjectMembershipRole, TableName.IdentityProjectMembershipRole,
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`, `${TableName.IdentityProjectMembershipRole}.projectMembershipId`,

View File

@ -10,7 +10,8 @@ export const samlConfigDALFactory = (db: TDbClient) => {
const findEnforceableSamlCfg = async (orgId: string) => { const findEnforceableSamlCfg = async (orgId: string) => {
try { try {
const samlCfg = await db(TableName.SamlConfig) const samlCfg = await db
.replicaNode()(TableName.SamlConfig)
.where({ .where({
orgId, orgId,
isActive: true isActive: true

View File

@ -380,6 +380,21 @@ export const samlConfigServiceFactory = ({
return foundUser; return foundUser;
}); });
} else { } else {
const plan = await licenseService.getPlan(orgId);
if (plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
throw new BadRequestError({
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
});
}
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
throw new BadRequestError({
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
});
}
user = await userDAL.transaction(async (tx) => { user = await userDAL.transaction(async (tx) => {
let newUser: TUsers | undefined; let newUser: TUsers | undefined;
if (serverCfg.trustSamlEmails) { if (serverCfg.trustSamlEmails) {

View File

@ -30,7 +30,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
const findById = async (id: string, tx?: Knex) => { const findById = async (id: string, tx?: Knex) => {
try { try {
const doc = await sapFindQuery(tx || db, { const doc = await sapFindQuery(tx || db.replicaNode(), {
[`${TableName.SecretApprovalPolicy}.id` as "id"]: id [`${TableName.SecretApprovalPolicy}.id` as "id"]: id
}); });
const formatedDoc = mergeOneToManyRelation( const formatedDoc = mergeOneToManyRelation(
@ -52,7 +52,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
const find = async (filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>, tx?: Knex) => { const find = async (filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>, tx?: Knex) => {
try { try {
const docs = await sapFindQuery(tx || db, filter); const docs = await sapFindQuery(tx || db.replicaNode(), filter);
const formatedDoc = mergeOneToManyRelation( const formatedDoc = mergeOneToManyRelation(
docs, docs,
"id", "id",

View File

@ -62,7 +62,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
const findById = async (id: string, tx?: Knex) => { const findById = async (id: string, tx?: Knex) => {
try { try {
const sql = findQuery({ [`${TableName.SecretApprovalRequest}.id` as "id"]: id }, tx || db); const sql = findQuery({ [`${TableName.SecretApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode());
const docs = await sql; const docs = await sql;
const formatedDoc = sqlNestRelationships({ const formatedDoc = sqlNestRelationships({
data: docs, data: docs,
@ -102,7 +102,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
const docs = await (tx || db) const docs = await (tx || db)
.with( .with(
"temp", "temp",
(tx || db)(TableName.SecretApprovalRequest) (tx || db.replicaNode())(TableName.SecretApprovalRequest)
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`) .join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.join( .join(
@ -148,7 +148,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
try { try {
// akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination // akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination
// this is the place u wanna look at. // this is the place u wanna look at.
const query = (tx || db)(TableName.SecretApprovalRequest) const query = (tx || db.replicaNode())(TableName.SecretApprovalRequest)
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`) .join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.join( .join(

View File

@ -47,7 +47,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
const findByRequestId = async (requestId: string, tx?: Knex) => { const findByRequestId = async (requestId: string, tx?: Knex) => {
try { try {
const doc = await (tx || db)({ const doc = await (tx || db.replicaNode())({
secVerTag: TableName.SecretTag secVerTag: TableName.SecretTag
}) })
.from(TableName.SecretApprovalRequestSecret) .from(TableName.SecretApprovalRequestSecret)

View File

@ -41,7 +41,7 @@ export const secretRotationDALFactory = (db: TDbClient) => {
const find = async (filter: TFindFilter<TSecretRotations & { projectId: string }>, tx?: Knex) => { const find = async (filter: TFindFilter<TSecretRotations & { projectId: string }>, tx?: Knex) => {
try { try {
const data = await findQuery(filter, tx || db); const data = await findQuery(filter, tx || db.replicaNode());
return sqlNestRelationships({ return sqlNestRelationships({
data, data,
key: "id", key: "id",
@ -93,7 +93,7 @@ export const secretRotationDALFactory = (db: TDbClient) => {
const findById = async (id: string, tx?: Knex) => { const findById = async (id: string, tx?: Knex) => {
try { try {
const doc = await (tx || db)(TableName.SecretRotation) const doc = await (tx || db.replicaNode())(TableName.SecretRotation)
.join(TableName.Environment, `${TableName.SecretRotation}.envId`, `${TableName.Environment}.id`) .join(TableName.Environment, `${TableName.SecretRotation}.envId`, `${TableName.Environment}.id`)
.where({ [`${TableName.SecretRotation}.id` as "id"]: id }) .where({ [`${TableName.SecretRotation}.id` as "id"]: id })
.select(selectAllTableCols(TableName.SecretRotation)) .select(selectAllTableCols(TableName.SecretRotation))

View File

@ -21,7 +21,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
const findById = async (id: string, tx?: Knex) => { const findById = async (id: string, tx?: Knex) => {
try { try {
const data = await (tx || db)(TableName.Snapshot) const data = await (tx || db.replicaNode())(TableName.Snapshot)
.where(`${TableName.Snapshot}.id`, id) .where(`${TableName.Snapshot}.id`, id)
.join(TableName.Environment, `${TableName.Snapshot}.envId`, `${TableName.Environment}.id`) .join(TableName.Environment, `${TableName.Snapshot}.envId`, `${TableName.Environment}.id`)
.select(selectAllTableCols(TableName.Snapshot)) .select(selectAllTableCols(TableName.Snapshot))
@ -43,7 +43,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
const countOfSnapshotsByFolderId = async (folderId: string, tx?: Knex) => { const countOfSnapshotsByFolderId = async (folderId: string, tx?: Knex) => {
try { try {
const doc = await (tx || db)(TableName.Snapshot) const doc = await (tx || db.replicaNode())(TableName.Snapshot)
.where({ folderId }) .where({ folderId })
.groupBy(["folderId"]) .groupBy(["folderId"])
.count("folderId") .count("folderId")
@ -56,7 +56,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
const findSecretSnapshotDataById = async (snapshotId: string, tx?: Knex) => { const findSecretSnapshotDataById = async (snapshotId: string, tx?: Knex) => {
try { try {
const data = await (tx || db)(TableName.Snapshot) const data = await (tx || db.replicaNode())(TableName.Snapshot)
.where(`${TableName.Snapshot}.id`, snapshotId) .where(`${TableName.Snapshot}.id`, snapshotId)
.join(TableName.Environment, `${TableName.Snapshot}.envId`, `${TableName.Environment}.id`) .join(TableName.Environment, `${TableName.Snapshot}.envId`, `${TableName.Environment}.id`)
.leftJoin(TableName.SnapshotSecret, `${TableName.Snapshot}.id`, `${TableName.SnapshotSecret}.snapshotId`) .leftJoin(TableName.SnapshotSecret, `${TableName.Snapshot}.id`, `${TableName.SnapshotSecret}.snapshotId`)
@ -309,7 +309,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
// when we need to rollback we will pull from these snapshots // when we need to rollback we will pull from these snapshots
const findLatestSnapshotByFolderId = async (folderId: string, tx?: Knex) => { const findLatestSnapshotByFolderId = async (folderId: string, tx?: Knex) => {
try { try {
const docs = await (tx || db)(TableName.Snapshot) const docs = await (tx || db.replicaNode())(TableName.Snapshot)
.where(`${TableName.Snapshot}.folderId`, folderId) .where(`${TableName.Snapshot}.folderId`, folderId)
.join<TSecretSnapshots>( .join<TSecretSnapshots>(
(tx || db)(TableName.Snapshot).groupBy("folderId").max("createdAt").select("folderId").as("latestVersion"), (tx || db)(TableName.Snapshot).groupBy("folderId").max("createdAt").select("folderId").as("latestVersion"),

View File

@ -42,6 +42,13 @@ export const IDENTITIES = {
}, },
DELETE: { DELETE: {
identityId: "The ID of the identity to delete." identityId: "The ID of the identity to delete."
},
GET_BY_ID: {
identityId: "The ID of the identity to get details.",
orgId: "The ID of the org of the identity"
},
LIST: {
orgId: "The ID of the organization to list identities."
} }
} as const; } as const;
@ -65,6 +72,9 @@ export const UNIVERSAL_AUTH = {
RETRIEVE: { RETRIEVE: {
identityId: "The ID of the identity to retrieve." identityId: "The ID of the identity to retrieve."
}, },
REVOKE: {
identityId: "The ID of the identity to revoke."
},
UPDATE: { UPDATE: {
identityId: "The ID of the identity to update.", identityId: "The ID of the identity to update.",
clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.", clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.",
@ -83,6 +93,10 @@ export const UNIVERSAL_AUTH = {
LIST_CLIENT_SECRETS: { LIST_CLIENT_SECRETS: {
identityId: "The ID of the identity to list client secrets for." identityId: "The ID of the identity to list client secrets for."
}, },
GET_CLIENT_SECRET: {
identityId: "The ID of the identity to get the client secret from.",
clientSecretId: "The ID of the client secret to get details."
},
REVOKE_CLIENT_SECRET: { REVOKE_CLIENT_SECRET: {
identityId: "The ID of the identity to revoke the client secret from.", identityId: "The ID of the identity to revoke the client secret from.",
clientSecretId: "The ID of the client secret to revoke." clientSecretId: "The ID of the client secret to revoke."
@ -104,6 +118,27 @@ export const AWS_AUTH = {
iamRequestBody: iamRequestBody:
"The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.", "The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.",
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request." iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
},
REVOKE: {
identityId: "The ID of the identity to revoke."
}
} as const;
export const AZURE_AUTH = {
REVOKE: {
identityId: "The ID of the identity to revoke."
}
} as const;
export const GCP_AUTH = {
REVOKE: {
identityId: "The ID of the identity to revoke."
}
} as const;
export const KUBERNETES_AUTH = {
REVOKE: {
identityId: "The ID of the identity to revoke."
} }
} as const; } as const;
@ -347,6 +382,7 @@ export const RAW_SECRETS = {
tagIds: "The ID of the tags to be attached to the created secret." tagIds: "The ID of the tags to be attached to the created secret."
}, },
GET: { GET: {
expand: "Whether or not to expand secret references",
secretName: "The name of the secret to get.", secretName: "The name of the secret to get.",
workspaceId: "The ID of the project to get the secret from.", workspaceId: "The ID of the project to get the secret from.",
workspaceSlug: "The slug of the project to get the secret from.", workspaceSlug: "The slug of the project to get the secret from.",
@ -656,6 +692,7 @@ export const INTEGRATION_AUTH = {
integration: "The slug of integration for the auth object.", integration: "The slug of integration for the auth object.",
accessId: "The unique authorized access id of the external integration provider.", accessId: "The unique authorized access id of the external integration provider.",
accessToken: "The unique authorized access token of the external integration provider.", accessToken: "The unique authorized access token of the external integration provider.",
awsAssumeIamRoleArn: "The AWS IAM Role to be assumed by Infisical",
url: "", url: "",
namespace: "", namespace: "",
refreshToken: "The refresh token for integration authorization." refreshToken: "The refresh token for integration authorization."
@ -804,6 +841,8 @@ export const CERTIFICATE_AUTHORITIES = {
caId: "The ID of the CA to issue the certificate from", caId: "The ID of the CA to issue the certificate from",
friendlyName: "A friendly name for the certificate", friendlyName: "A friendly name for the certificate",
commonName: "The common name (CN) for the certificate", commonName: "The common name (CN) for the certificate",
altNames:
"A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses.",
ttl: "The time to live for the certificate such as 1m, 1h, 1d, 1y, ...", ttl: "The time to live for the certificate such as 1m, 1h, 1d, 1y, ...",
notBefore: "The date and time when the certificate becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format", notBefore: "The date and time when the certificate becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format",
notAfter: "The date and time when the certificate expires in YYYY-MM-DDTHH:mm:ss.sssZ format", notAfter: "The date and time when the certificate expires in YYYY-MM-DDTHH:mm:ss.sssZ format",

View File

@ -10,6 +10,14 @@ const zodStrBool = z
.optional() .optional()
.transform((val) => val === "true"); .transform((val) => val === "true");
const databaseReadReplicaSchema = z
.object({
DB_CONNECTION_URI: z.string().describe("Postgres read replica database connection string"),
DB_ROOT_CERT: zpStr(z.string().optional().describe("Postgres read replica database certificate string"))
})
.array()
.optional();
const envSchema = z const envSchema = z
.object({ .object({
PORT: z.coerce.number().default(4000), PORT: z.coerce.number().default(4000),
@ -29,6 +37,7 @@ const envSchema = z
DB_USER: zpStr(z.string().describe("Postgres database username").optional()), DB_USER: zpStr(z.string().describe("Postgres database username").optional()),
DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()), DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()),
DB_NAME: zpStr(z.string().describe("Postgres database name").optional()), DB_NAME: zpStr(z.string().describe("Postgres database name").optional()),
DB_READ_REPLICAS: zpStr(z.string().describe("Postgres read replicas").optional()),
BCRYPT_SALT_ROUND: z.number().default(12), BCRYPT_SALT_ROUND: z.number().default(12),
NODE_ENV: z.enum(["development", "test", "production"]).default("production"), NODE_ENV: z.enum(["development", "test", "production"]).default("production"),
SALT_ROUNDS: z.coerce.number().default(10), SALT_ROUNDS: z.coerce.number().default(10),
@ -101,6 +110,9 @@ const envSchema = z
// azure // azure
CLIENT_ID_AZURE: zpStr(z.string().optional()), CLIENT_ID_AZURE: zpStr(z.string().optional()),
CLIENT_SECRET_AZURE: zpStr(z.string().optional()), CLIENT_SECRET_AZURE: zpStr(z.string().optional()),
// aws
CLIENT_ID_AWS_INTEGRATION: zpStr(z.string().optional()),
CLIENT_SECRET_AWS_INTEGRATION: zpStr(z.string().optional()),
// gitlab // gitlab
CLIENT_ID_GITLAB: zpStr(z.string().optional()), CLIENT_ID_GITLAB: zpStr(z.string().optional()),
CLIENT_SECRET_GITLAB: zpStr(z.string().optional()), CLIENT_SECRET_GITLAB: zpStr(z.string().optional()),
@ -127,6 +139,9 @@ const envSchema = z
}) })
.transform((data) => ({ .transform((data) => ({
...data, ...data,
DB_READ_REPLICAS: data.DB_READ_REPLICAS
? databaseReadReplicaSchema.parse(JSON.parse(data.DB_READ_REPLICAS))
: undefined,
isCloud: Boolean(data.LICENSE_SERVER_KEY), isCloud: Boolean(data.LICENSE_SERVER_KEY),
isSmtpConfigured: Boolean(data.SMTP_HOST), isSmtpConfigured: Boolean(data.SMTP_HOST),
isRedisConfigured: Boolean(data.REDIS_URL), isRedisConfigured: Boolean(data.REDIS_URL),

View File

@ -50,7 +50,7 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(db: Kne
}), }),
findById: async (id: string, tx?: Knex) => { findById: async (id: string, tx?: Knex) => {
try { try {
const result = await (tx || db)(tableName) const result = await (tx || db.replicaNode())(tableName)
.where({ id } as never) .where({ id } as never)
.first("*"); .first("*");
return result; return result;
@ -60,7 +60,7 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(db: Kne
}, },
findOne: async (filter: Partial<Tables[Tname]["base"]>, tx?: Knex) => { findOne: async (filter: Partial<Tables[Tname]["base"]>, tx?: Knex) => {
try { try {
const res = await (tx || db)(tableName).where(filter).first("*"); const res = await (tx || db.replicaNode())(tableName).where(filter).first("*");
return res; return res;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Find one" }); throw new DatabaseError({ error, name: "Find one" });
@ -71,7 +71,7 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(db: Kne
{ offset, limit, sort, tx }: TFindOpt<Tables[Tname]["base"]> = {} { offset, limit, sort, tx }: TFindOpt<Tables[Tname]["base"]> = {}
) => { ) => {
try { try {
const query = (tx || db)(tableName).where(buildFindFilter(filter)); const query = (tx || db.replicaNode())(tableName).where(buildFindFilter(filter));
if (limit) void query.limit(limit); if (limit) void query.limit(limit);
if (offset) void query.offset(offset); if (offset) void query.offset(offset);
if (sort) { if (sort) {

View File

@ -15,7 +15,11 @@ const run = async () => {
const appCfg = initEnvConfig(logger); const appCfg = initEnvConfig(logger);
const db = initDbConnection({ const db = initDbConnection({
dbConnectionUri: appCfg.DB_CONNECTION_URI, dbConnectionUri: appCfg.DB_CONNECTION_URI,
dbRootCert: appCfg.DB_ROOT_CERT dbRootCert: appCfg.DB_ROOT_CERT,
readReplicas: appCfg.DB_READ_REPLICAS?.map((el) => ({
dbRootCert: el.DB_ROOT_CERT,
dbConnectionUri: el.DB_CONNECTION_URI
}))
}); });
const smtp = smtpServiceFactory(formatSmtpConfig()); const smtp = smtpServiceFactory(formatSmtpConfig());

View File

@ -32,6 +32,8 @@ import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-conf
import { ldapGroupMapDALFactory } from "@app/ee/services/ldap-config/ldap-group-map-dal"; import { ldapGroupMapDALFactory } from "@app/ee/services/ldap-config/ldap-group-map-dal";
import { licenseDALFactory } from "@app/ee/services/license/license-dal"; import { licenseDALFactory } from "@app/ee/services/license/license-dal";
import { licenseServiceFactory } from "@app/ee/services/license/license-service"; import { licenseServiceFactory } from "@app/ee/services/license/license-service";
import { oidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
import { oidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-service";
import { permissionDALFactory } from "@app/ee/services/permission/permission-dal"; import { permissionDALFactory } from "@app/ee/services/permission/permission-dal";
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { projectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal"; import { projectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
@ -250,6 +252,7 @@ export const registerRoutes = async (
const ldapConfigDAL = ldapConfigDALFactory(db); const ldapConfigDAL = ldapConfigDALFactory(db);
const ldapGroupMapDAL = ldapGroupMapDALFactory(db); const ldapGroupMapDAL = ldapGroupMapDALFactory(db);
const oidcConfigDAL = oidcConfigDALFactory(db);
const accessApprovalPolicyDAL = accessApprovalPolicyDALFactory(db); const accessApprovalPolicyDAL = accessApprovalPolicyDALFactory(db);
const accessApprovalRequestDAL = accessApprovalRequestDALFactory(db); const accessApprovalRequestDAL = accessApprovalRequestDALFactory(db);
const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db); const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db);
@ -392,7 +395,9 @@ export const registerRoutes = async (
userDAL, userDAL,
userAliasDAL, userAliasDAL,
permissionService, permissionService,
licenseService licenseService,
tokenService,
smtpService
}); });
const telemetryService = telemetryServiceFactory({ const telemetryService = telemetryServiceFactory({
@ -801,7 +806,8 @@ export const registerRoutes = async (
const identityService = identityServiceFactory({ const identityService = identityServiceFactory({
permissionService, permissionService,
identityDAL, identityDAL,
identityOrgMembershipDAL identityOrgMembershipDAL,
licenseService
}); });
const identityAccessTokenService = identityAccessTokenServiceFactory({ const identityAccessTokenService = identityAccessTokenServiceFactory({
identityAccessTokenDAL, identityAccessTokenDAL,
@ -903,6 +909,19 @@ export const registerRoutes = async (
secretSharingDAL secretSharingDAL
}); });
const oidcService = oidcConfigServiceFactory({
orgDAL,
orgMembershipDAL,
userDAL,
userAliasDAL,
licenseService,
tokenService,
smtpService,
orgBotDAL,
permissionService,
oidcConfigDAL
});
await superAdminService.initServerCfg(); await superAdminService.initServerCfg();
// //
// setup the communication with license key server // setup the communication with license key server
@ -923,6 +942,7 @@ export const registerRoutes = async (
permission: permissionService, permission: permissionService,
org: orgService, org: orgService,
orgRole: orgRoleService, orgRole: orgRoleService,
oidc: oidcService,
apiKey: apiKeyService, apiKey: apiKeyService,
authToken: tokenService, authToken: tokenService,
superAdmin: superAdminService, superAdmin: superAdminService,

View File

@ -22,6 +22,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
200: z.object({ 200: z.object({
config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true }).extend({ config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true }).extend({
isMigrationModeOn: z.boolean(), isMigrationModeOn: z.boolean(),
defaultAuthOrgSlug: z.string().nullable(),
isSecretScanningDisabled: z.boolean() isSecretScanningDisabled: z.boolean()
}) })
}) })
@ -51,11 +52,15 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
allowSignUp: z.boolean().optional(), allowSignUp: z.boolean().optional(),
allowedSignUpDomain: z.string().optional().nullable(), allowedSignUpDomain: z.string().optional().nullable(),
trustSamlEmails: z.boolean().optional(), trustSamlEmails: z.boolean().optional(),
trustLdapEmails: z.boolean().optional() trustLdapEmails: z.boolean().optional(),
trustOidcEmails: z.boolean().optional(),
defaultAuthOrgId: z.string().optional().nullable()
}), }),
response: { response: {
200: z.object({ 200: z.object({
config: SuperAdminSchema config: SuperAdminSchema.extend({
defaultAuthOrgSlug: z.string().nullable()
})
}) })
} }
}, },

View File

@ -9,7 +9,10 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types"; import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
import { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types"; import { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators"; import {
validateAltNamesField,
validateCaDateField
} from "@app/services/certificate-authority/certificate-authority-validators";
export const registerCaRouter = async (server: FastifyZodProvider) => { export const registerCaRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
@ -452,6 +455,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
.object({ .object({
friendlyName: z.string().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName), friendlyName: z.string().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName),
commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName), commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName),
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.altNames),
ttl: z ttl: z
.string() .string()
.refine((val) => ms(val) > 0, "TTL must be a positive number") .refine((val) => ms(val) > 0, "TTL must be a positive number")

View File

@ -266,4 +266,51 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
return { identityAwsAuth }; return { identityAwsAuth };
} }
}); });
server.route({
method: "DELETE",
url: "/aws-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete AWS Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(AWS_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityAwsAuth: IdentityAwsAuthsSchema
})
}
},
handler: async (req) => {
const identityAwsAuth = await server.services.identityAwsAuth.revokeIdentityAwsAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityAwsAuth.orgId,
event: {
type: EventType.REVOKE_IDENTITY_AWS_AUTH,
metadata: {
identityId: identityAwsAuth.identityId
}
}
});
return { identityAwsAuth };
}
});
}; };

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { IdentityAzureAuthsSchema } from "@app/db/schemas"; import { IdentityAzureAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { AZURE_AUTH } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@ -259,4 +260,51 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
return { identityAzureAuth }; return { identityAzureAuth };
} }
}); });
server.route({
method: "DELETE",
url: "/azure-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete Azure Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(AZURE_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityAzureAuth: IdentityAzureAuthsSchema
})
}
},
handler: async (req) => {
const identityAzureAuth = await server.services.identityAzureAuth.revokeIdentityAzureAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityAzureAuth.orgId,
event: {
type: EventType.REVOKE_IDENTITY_AZURE_AUTH,
metadata: {
identityId: identityAzureAuth.identityId
}
}
});
return { identityAzureAuth };
}
});
}; };

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { IdentityGcpAuthsSchema } from "@app/db/schemas"; import { IdentityGcpAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { GCP_AUTH } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@ -265,4 +266,51 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
return { identityGcpAuth }; return { identityGcpAuth };
} }
}); });
server.route({
method: "DELETE",
url: "/gcp-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete GCP Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(GCP_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityGcpAuth: IdentityGcpAuthsSchema
})
}
},
handler: async (req) => {
const identityGcpAuth = await server.services.identityGcpAuth.revokeIdentityGcpAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityGcpAuth.orgId,
event: {
type: EventType.REVOKE_IDENTITY_GCP_AUTH,
metadata: {
identityId: identityGcpAuth.identityId
}
}
});
return { identityGcpAuth };
}
});
}; };

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { IdentityKubernetesAuthsSchema } from "@app/db/schemas"; import { IdentityKubernetesAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { KUBERNETES_AUTH } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@ -280,4 +281,54 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
return { identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.parse(identityKubernetesAuth) }; return { identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.parse(identityKubernetesAuth) };
} }
}); });
server.route({
method: "DELETE",
url: "/kubernetes-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete Kubernetes Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(KUBERNETES_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.omit({
caCert: true,
tokenReviewerJwt: true
})
})
}
},
handler: async (req) => {
const identityKubernetesAuth = await server.services.identityKubernetesAuth.revokeIdentityKubernetesAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityKubernetesAuth.orgId,
event: {
type: EventType.REVOKE_IDENTITY_KUBERNETES_AUTH,
metadata: {
identityId: identityKubernetesAuth.identityId
}
}
});
return { identityKubernetesAuth };
}
});
}; };

View File

@ -1,9 +1,9 @@
import { z } from "zod"; import { z } from "zod";
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas"; import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgMembershipRole, OrgRolesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { IDENTITIES } from "@app/lib/api-docs"; import { IDENTITIES } from "@app/lib/api-docs";
import { creationLimit, writeLimit } from "@app/server/config/rateLimiter"; import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@ -170,4 +170,94 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
return { identity }; return { identity };
} }
}); });
server.route({
method: "GET",
url: "/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Get an identity by id",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(IDENTITIES.GET_BY_ID.identityId)
}),
response: {
200: z.object({
identity: IdentityOrgMembershipsSchema.extend({
customRole: OrgRolesSchema.pick({
id: true,
name: true,
slug: true,
permissions: true,
description: true
}).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
})
})
}
},
handler: async (req) => {
const identity = await server.services.identity.getIdentityById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.identityId
});
return { identity };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "List identities",
security: [
{
bearerAuth: []
}
],
querystring: z.object({
orgId: z.string().describe(IDENTITIES.LIST.orgId)
}),
response: {
200: z.object({
identities: IdentityOrgMembershipsSchema.extend({
customRole: OrgRolesSchema.pick({
id: true,
name: true,
slug: true,
permissions: true,
description: true
}).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
}).array()
})
}
},
handler: async (req) => {
const identities = await server.services.identity.listOrgIdentities({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.query.orgId
});
return { identities };
}
});
}; };

View File

@ -134,7 +134,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.attachUa({ const identityUniversalAuth = await server.services.identityUa.attachUniversalAuth({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
@ -219,7 +219,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.updateUa({ const identityUniversalAuth = await server.services.identityUa.updateUniversalAuth({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
@ -272,7 +272,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.getIdentityUa({ const identityUniversalAuth = await server.services.identityUa.getIdentityUniversalAuth({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
@ -295,6 +295,53 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "DELETE",
url: "/universal-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete Universal Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(UNIVERSAL_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityUniversalAuth: IdentityUniversalAuthsSchema
})
}
},
handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.revokeIdentityUniversalAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityUniversalAuth.orgId,
event: {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId
}
}
});
return { identityUniversalAuth };
}
});
server.route({ server.route({
method: "POST", method: "POST",
url: "/universal-auth/identities/:identityId/client-secrets", url: "/universal-auth/identities/:identityId/client-secrets",
@ -325,7 +372,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const { clientSecret, clientSecretData, orgId } = await server.services.identityUa.createUaClientSecret({ const { clientSecret, clientSecretData, orgId } =
await server.services.identityUa.createUniversalAuthClientSecret({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
@ -374,13 +422,15 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const { clientSecrets: clientSecretData, orgId } = await server.services.identityUa.getUaClientSecrets({ const { clientSecrets: clientSecretData, orgId } = await server.services.identityUa.getUniversalAuthClientSecrets(
{
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
identityId: req.params.identityId identityId: req.params.identityId
}); }
);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
@ -396,6 +446,56 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "GET",
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Get Universal Auth Client Secret for identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(UNIVERSAL_AUTH.GET_CLIENT_SECRET.identityId),
clientSecretId: z.string().describe(UNIVERSAL_AUTH.GET_CLIENT_SECRET.clientSecretId)
}),
response: {
200: z.object({
clientSecretData: sanitizedClientSecretSchema
})
}
},
handler: async (req) => {
const clientSecretData = await server.services.identityUa.getUniversalAuthClientSecretById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
clientSecretId: req.params.clientSecretId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: clientSecretData.orgId,
event: {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
metadata: {
identityId: clientSecretData.identityId,
clientSecretId: clientSecretData.id
}
}
});
return { clientSecretData };
}
});
server.route({ server.route({
method: "POST", method: "POST",
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke", url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
@ -421,7 +521,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const clientSecretData = await server.services.identityUa.revokeUaClientSecret({ const clientSecretData = await server.services.identityUa.revokeUniversalAuthClientSecret({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,

View File

@ -9,7 +9,7 @@ import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router"; import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router"; import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
import { registerIdentityRouter } from "./identity-router"; import { registerIdentityRouter } from "./identity-router";
import { registerIdentityUaRouter } from "./identity-ua"; import { registerIdentityUaRouter } from "./identity-universal-auth-router";
import { registerIntegrationAuthRouter } from "./integration-auth-router"; import { registerIntegrationAuthRouter } from "./integration-auth-router";
import { registerIntegrationRouter } from "./integration-router"; import { registerIntegrationRouter } from "./integration-router";
import { registerInviteOrgRouter } from "./invite-org-router"; import { registerInviteOrgRouter } from "./invite-org-router";

View File

@ -240,6 +240,12 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
integration: z.string().trim().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.integration), integration: z.string().trim().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.integration),
accessId: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.accessId), accessId: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.accessId),
accessToken: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.accessToken), accessToken: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.accessToken),
awsAssumeIamRoleArn: z
.string()
.url()
.trim()
.optional()
.describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.awsAssumeIamRoleArn),
url: z.string().url().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.url), url: z.string().url().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.url),
namespace: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.namespace), namespace: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.namespace),
refreshToken: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.refreshToken) refreshToken: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.refreshToken)

View File

@ -372,6 +372,44 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "PUT",
url: "/:workspaceSlug/audit-logs-retention",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceSlug: z.string().trim()
}),
body: z.object({
auditLogsRetentionDays: z.number().min(0)
}),
response: {
200: z.object({
message: z.string(),
workspace: ProjectsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.updateAuditLogsRetention({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
workspaceSlug: req.params.workspaceSlug,
auditLogsRetentionDays: req.body.auditLogsRetentionDays
});
return {
message: "Successfully updated project's audit logs retention period",
workspace
};
}
});
server.route({ server.route({
method: "GET", method: "GET",
url: "/:workspaceId/integrations", url: "/:workspaceId/integrations",

View File

@ -300,6 +300,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath), secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version), version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type), type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
expandSecretReferences: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
.describe(RAW_SECRETS.GET.expand),
include_imports: z include_imports: z
.enum(["true", "false"]) .enum(["true", "false"])
.default("false") .default("false")
@ -344,6 +349,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type, actor: req.permission.type,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
expandSecretReferences: req.query.expandSecretReferences,
environment, environment,
projectId: workspaceId, projectId: workspaceId,
projectSlug: workspaceSlug, projectSlug: workspaceSlug,

View File

@ -14,7 +14,7 @@ export const tokenDALFactory = (db: TDbClient) => {
const findOneTokenSession = async (filter: Partial<TAuthTokenSessions>): Promise<TAuthTokenSessions | undefined> => { const findOneTokenSession = async (filter: Partial<TAuthTokenSessions>): Promise<TAuthTokenSessions | undefined> => {
try { try {
const doc = await db(TableName.AuthTokenSession).where(filter).first(); const doc = await db.replicaNode()(TableName.AuthTokenSession).where(filter).first();
return doc; return doc;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "FindOneTokenSession" }); throw new DatabaseError({ error, name: "FindOneTokenSession" });
@ -44,7 +44,7 @@ export const tokenDALFactory = (db: TDbClient) => {
const findTokenSessions = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => { const findTokenSessions = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
try { try {
const sessions = await (tx || db)(TableName.AuthTokenSession).where(filter); const sessions = await (tx || db.replicaNode())(TableName.AuthTokenSession).where(filter);
return sessions; return sessions;
} catch (error) { } catch (error) {
throw new DatabaseError({ name: "Find all token session", error }); throw new DatabaseError({ name: "Find all token session", error });

View File

@ -202,7 +202,10 @@ export const authLoginServiceFactory = ({
const decodedProviderToken = validateProviderAuthToken(providerAuthToken, email); const decodedProviderToken = validateProviderAuthToken(providerAuthToken, email);
authMethod = decodedProviderToken.authMethod; authMethod = decodedProviderToken.authMethod;
if ((isAuthMethodSaml(authMethod) || authMethod === AuthMethod.LDAP) && decodedProviderToken.orgId) { if (
(isAuthMethodSaml(authMethod) || [AuthMethod.LDAP, AuthMethod.OIDC].includes(authMethod)) &&
decodedProviderToken.orgId
) {
organizationId = decodedProviderToken.orgId; organizationId = decodedProviderToken.orgId;
} }
} }
@ -351,9 +354,12 @@ export const authLoginServiceFactory = ({
// Check if the user actually has access to the specified organization. // Check if the user actually has access to the specified organization.
const userOrgs = await orgDAL.findAllOrgsByUserId(user.id); const userOrgs = await orgDAL.findAllOrgsByUserId(user.id);
const hasOrganizationMembership = userOrgs.some((org) => org.id === organizationId); const hasOrganizationMembership = userOrgs.some((org) => org.id === organizationId);
const selectedOrg = await orgDAL.findById(organizationId);
if (!hasOrganizationMembership) { if (!hasOrganizationMembership) {
throw new UnauthorizedError({ message: "User does not have access to the organization" }); throw new UnauthorizedError({
message: `User does not have access to the organization named ${selectedOrg?.name}`
});
} }
await tokenDAL.incrementTokenSessionVersion(user.id, decodedToken.tokenVersionId); await tokenDAL.incrementTokenSessionVersion(user.id, decodedToken.tokenVersionId);
@ -578,7 +584,8 @@ export const authLoginServiceFactory = ({
const { authMethod, userName } = decodedProviderToken; const { authMethod, userName } = decodedProviderToken;
if (!userName) throw new BadRequestError({ message: "Missing user name" }); if (!userName) throw new BadRequestError({ message: "Missing user name" });
const organizationId = const organizationId =
(isAuthMethodSaml(authMethod) || authMethod === AuthMethod.LDAP) && decodedProviderToken.orgId (isAuthMethodSaml(authMethod) || [AuthMethod.LDAP, AuthMethod.OIDC].includes(authMethod)) &&
decodedProviderToken.orgId
? decodedProviderToken.orgId ? decodedProviderToken.orgId
: undefined; : undefined;

View File

@ -193,7 +193,10 @@ export const authSignupServiceFactory = ({
tx tx
); );
// If it's SAML Auth and the organization ID is present, we should check if the user has a pending invite for this org, and accept it // If it's SAML Auth and the organization ID is present, we should check if the user has a pending invite for this org, and accept it
if ((isAuthMethodSaml(authMethod) || authMethod === AuthMethod.LDAP) && organizationId) { if (
(isAuthMethodSaml(authMethod) || [AuthMethod.LDAP, AuthMethod.OIDC].includes(authMethod as AuthMethod)) &&
organizationId
) {
const [pendingOrgMembership] = await orgDAL.findMembership({ const [pendingOrgMembership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.userId` as "userId"]: user.id, [`${TableName.OrgMembership}.userId` as "userId"]: user.id,
status: OrgMembershipStatus.Invited, status: OrgMembershipStatus.Invited,

View File

@ -8,7 +8,8 @@ export enum AuthMethod {
JUMPCLOUD_SAML = "jumpcloud-saml", JUMPCLOUD_SAML = "jumpcloud-saml",
GOOGLE_SAML = "google-saml", GOOGLE_SAML = "google-saml",
KEYCLOAK_SAML = "keycloak-saml", KEYCLOAK_SAML = "keycloak-saml",
LDAP = "ldap" LDAP = "ldap",
OIDC = "oidc"
} }
export enum AuthTokenType { export enum AuthTokenType {

View File

@ -16,6 +16,7 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
parentCaId?: string; parentCaId?: string;
encryptedCertificate: Buffer; encryptedCertificate: Buffer;
}[] = await db }[] = await db
.replicaNode()
.withRecursive("cte", (cte) => { .withRecursive("cte", (cte) => {
void cte void cte
.select("ca.id as caId", "ca.parentCaId", "cert.encryptedCertificate") .select("ca.id as caId", "ca.parentCaId", "cert.encryptedCertificate")

View File

@ -3,6 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import crypto, { KeyObject } from "crypto"; import crypto, { KeyObject } from "crypto";
import ms from "ms"; import ms from "ms";
import { z } from "zod";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -38,6 +39,7 @@ import {
TSignIntermediateDTO, TSignIntermediateDTO,
TUpdateCaDTO TUpdateCaDTO
} from "./certificate-authority-types"; } from "./certificate-authority-types";
import { hostnameRegex } from "./certificate-authority-validators";
type TCertificateAuthorityServiceFactoryDep = { type TCertificateAuthorityServiceFactoryDep = {
certificateAuthorityDAL: Pick< certificateAuthorityDAL: Pick<
@ -653,6 +655,7 @@ export const certificateAuthorityServiceFactory = ({
caId, caId,
friendlyName, friendlyName,
commonName, commonName,
altNames,
ttl, ttl,
notBefore, notBefore,
notAfter, notAfter,
@ -738,6 +741,45 @@ export const certificateAuthorityServiceFactory = ({
kmsService kmsService
}); });
const extensions: x509.Extension[] = [
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
new x509.BasicConstraintsExtension(false),
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
];
if (altNames) {
const altNamesArray: {
type: "email" | "dns";
value: string;
}[] = altNames
.split(",")
.map((name) => name.trim())
.map((altName) => {
// check if the altName is a valid email
if (z.string().email().safeParse(altName).success) {
return {
type: "email",
value: altName
};
}
// check if the altName is a valid hostname
if (hostnameRegex.test(altName)) {
return {
type: "dns",
value: altName
};
}
// If altName is neither a valid email nor a valid hostname, throw an error or handle it accordingly
throw new Error(`Invalid altName: ${altName}`);
});
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
extensions.push(altNamesExtension);
}
const serialNumber = crypto.randomBytes(32).toString("hex"); const serialNumber = crypto.randomBytes(32).toString("hex");
const leafCert = await x509.X509CertificateGenerator.create({ const leafCert = await x509.X509CertificateGenerator.create({
serialNumber, serialNumber,
@ -748,12 +790,7 @@ export const certificateAuthorityServiceFactory = ({
signingKey: caPrivateKey, signingKey: caPrivateKey,
publicKey: csrObj.publicKey, publicKey: csrObj.publicKey,
signingAlgorithm: alg, signingAlgorithm: alg,
extensions: [ extensions
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
new x509.BasicConstraintsExtension(false),
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
]
}); });
const skLeafObj = KeyObject.from(leafKeys.privateKey); const skLeafObj = KeyObject.from(leafKeys.privateKey);
@ -771,6 +808,7 @@ export const certificateAuthorityServiceFactory = ({
status: CertStatus.ACTIVE, status: CertStatus.ACTIVE,
friendlyName: friendlyName || commonName, friendlyName: friendlyName || commonName,
commonName, commonName,
altNames,
serialNumber, serialNumber,
notBefore: notBeforeDate, notBefore: notBeforeDate,
notAfter: notAfterDate notAfter: notAfterDate

View File

@ -75,6 +75,7 @@ export type TIssueCertFromCaDTO = {
caId: string; caId: string;
friendlyName?: string; friendlyName?: string;
commonName: string; commonName: string;
altNames: string;
ttl: string; ttl: string;
notBefore?: string; notBefore?: string;
notAfter?: string; notAfter?: string;

View File

@ -6,3 +6,29 @@ const isValidDate = (dateString: string) => {
}; };
export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" }); export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" });
export const hostnameRegex = /^(?!:\/\/)([a-zA-Z0-9-_]{1,63}\.?)+(?!:\/\/)([a-zA-Z]{2,63})$/;
export const validateAltNamesField = z
.string()
.trim()
.default("")
.transform((data) => {
if (data === "") return "";
// Trim each alt name and join with ', ' to ensure formatting
return data
.split(",")
.map((id) => id.trim())
.join(", ");
})
.refine(
(data) => {
if (data === "") return true;
// Split and validate each alt name
return data.split(", ").every((name) => {
return hostnameRegex.test(name) || z.string().email().safeParse(name).success;
});
},
{
message: "Each alt name must be a valid hostname or email address"
}
);

View File

@ -14,7 +14,8 @@ export const certificateDALFactory = (db: TDbClient) => {
count: string; count: string;
} }
const count = await db(TableName.Certificate) const count = await db
.replicaNode()(TableName.Certificate)
.join(TableName.CertificateAuthority, `${TableName.Certificate}.caId`, `${TableName.CertificateAuthority}.id`) .join(TableName.CertificateAuthority, `${TableName.Certificate}.caId`, `${TableName.CertificateAuthority}.id`)
.join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`) .join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`)
.where(`${TableName.Project}.id`, projectId) .where(`${TableName.Project}.id`, projectId)

View File

@ -12,7 +12,7 @@ export const groupProjectDALFactory = (db: TDbClient) => {
const findByProjectId = async (projectId: string, tx?: Knex) => { const findByProjectId = async (projectId: string, tx?: Knex) => {
try { try {
const docs = await (tx || db)(TableName.GroupProjectMembership) const docs = await (tx || db.replicaNode())(TableName.GroupProjectMembership)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId) .where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.join(TableName.Groups, `${TableName.GroupProjectMembership}.groupId`, `${TableName.Groups}.id`) .join(TableName.Groups, `${TableName.GroupProjectMembership}.groupId`, `${TableName.Groups}.id`)
.join( .join(

View File

@ -12,7 +12,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
const findOne = async (filter: Partial<TIdentityAccessTokens>, tx?: Knex) => { const findOne = async (filter: Partial<TIdentityAccessTokens>, tx?: Knex) => {
try { try {
const doc = await (tx || db)(TableName.IdentityAccessToken) const doc = await (tx || db.replicaNode())(TableName.IdentityAccessToken)
.where(filter) .where(filter)
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`)
.leftJoin(TableName.IdentityUaClientSecret, (qb) => { .leftJoin(TableName.IdentityUaClientSecret, (qb) => {

View File

@ -7,11 +7,12 @@ import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
@ -24,12 +25,13 @@ import {
TGetAwsAuthDTO, TGetAwsAuthDTO,
TGetCallerIdentityResponse, TGetCallerIdentityResponse,
TLoginAwsAuthDTO, TLoginAwsAuthDTO,
TRevokeAwsAuthDTO,
TUpdateAwsAuthDTO TUpdateAwsAuthDTO
} from "./identity-aws-auth-types"; } from "./identity-aws-auth-types";
type TIdentityAwsAuthServiceFactoryDep = { type TIdentityAwsAuthServiceFactoryDep = {
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">; identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">; identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityDAL: Pick<TIdentityDALFactory, "updateById">; identityDAL: Pick<TIdentityDALFactory, "updateById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@ -301,10 +303,54 @@ export const identityAwsAuthServiceFactory = ({
return { ...awsIdentityAuth, orgId: identityMembershipOrg.orgId }; return { ...awsIdentityAuth, orgId: identityMembershipOrg.orgId };
}; };
const revokeIdentityAwsAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH)
throw new BadRequestError({
message: "The identity does not have aws auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to revoke aws auth of identity with more privileged role"
});
const revokedIdentityAwsAuth = await identityAwsAuthDAL.transaction(async (tx) => {
const deletedAwsAuth = await identityAwsAuthDAL.delete({ identityId }, tx);
await identityDAL.updateById(identityId, { authMethod: null }, tx);
return { ...deletedAwsAuth?.[0], orgId: identityMembershipOrg.orgId };
});
return revokedIdentityAwsAuth;
};
return { return {
login, login,
attachAwsAuth, attachAwsAuth,
updateAwsAuth, updateAwsAuth,
getAwsAuth getAwsAuth,
revokeIdentityAwsAuth
}; };
}; };

View File

@ -52,3 +52,7 @@ export type TGetCallerIdentityResponse = {
ResponseMetadata: { RequestId: string }; ResponseMetadata: { RequestId: string };
}; };
}; };
export type TRevokeAwsAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@ -5,11 +5,12 @@ import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
@ -20,11 +21,15 @@ import {
TAttachAzureAuthDTO, TAttachAzureAuthDTO,
TGetAzureAuthDTO, TGetAzureAuthDTO,
TLoginAzureAuthDTO, TLoginAzureAuthDTO,
TRevokeAzureAuthDTO,
TUpdateAzureAuthDTO TUpdateAzureAuthDTO
} from "./identity-azure-auth-types"; } from "./identity-azure-auth-types";
type TIdentityAzureAuthServiceFactoryDep = { type TIdentityAzureAuthServiceFactoryDep = {
identityAzureAuthDAL: Pick<TIdentityAzureAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">; identityAzureAuthDAL: Pick<
TIdentityAzureAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete"
>;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">; identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityDAL: Pick<TIdentityDALFactory, "updateById">; identityDAL: Pick<TIdentityDALFactory, "updateById">;
@ -277,10 +282,54 @@ export const identityAzureAuthServiceFactory = ({
return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId }; return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId };
}; };
const revokeIdentityAzureAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeAzureAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH)
throw new BadRequestError({
message: "The identity does not have azure auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to revoke azure auth of identity with more privileged role"
});
const revokedIdentityAzureAuth = await identityAzureAuthDAL.transaction(async (tx) => {
const deletedAzureAuth = await identityAzureAuthDAL.delete({ identityId }, tx);
await identityDAL.updateById(identityId, { authMethod: null }, tx);
return { ...deletedAzureAuth?.[0], orgId: identityMembershipOrg.orgId };
});
return revokedIdentityAzureAuth;
};
return { return {
login, login,
attachAzureAuth, attachAzureAuth,
updateAzureAuth, updateAzureAuth,
getAzureAuth getAzureAuth,
revokeIdentityAzureAuth
}; };
}; };

View File

@ -118,3 +118,7 @@ export type TDecodedAzureAuthJwt = {
[key: string]: string; [key: string]: string;
}; };
}; };
export type TRevokeAzureAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@ -5,11 +5,12 @@ import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
@ -21,11 +22,12 @@ import {
TGcpIdentityDetails, TGcpIdentityDetails,
TGetGcpAuthDTO, TGetGcpAuthDTO,
TLoginGcpAuthDTO, TLoginGcpAuthDTO,
TRevokeGcpAuthDTO,
TUpdateGcpAuthDTO TUpdateGcpAuthDTO
} from "./identity-gcp-auth-types"; } from "./identity-gcp-auth-types";
type TIdentityGcpAuthServiceFactoryDep = { type TIdentityGcpAuthServiceFactoryDep = {
identityGcpAuthDAL: Pick<TIdentityGcpAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">; identityGcpAuthDAL: Pick<TIdentityGcpAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">; identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityDAL: Pick<TIdentityDALFactory, "updateById">; identityDAL: Pick<TIdentityDALFactory, "updateById">;
@ -315,10 +317,54 @@ export const identityGcpAuthServiceFactory = ({
return { ...identityGcpAuth, orgId: identityMembershipOrg.orgId }; return { ...identityGcpAuth, orgId: identityMembershipOrg.orgId };
}; };
const revokeIdentityGcpAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeGcpAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.GCP_AUTH)
throw new BadRequestError({
message: "The identity does not have gcp auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to revoke gcp auth of identity with more privileged role"
});
const revokedIdentityGcpAuth = await identityGcpAuthDAL.transaction(async (tx) => {
const deletedGcpAuth = await identityGcpAuthDAL.delete({ identityId }, tx);
await identityDAL.updateById(identityId, { authMethod: null }, tx);
return { ...deletedGcpAuth?.[0], orgId: identityMembershipOrg.orgId };
});
return revokedIdentityGcpAuth;
};
return { return {
login, login,
attachGcpAuth, attachGcpAuth,
updateGcpAuth, updateGcpAuth,
getGcpAuth getGcpAuth,
revokeIdentityGcpAuth
}; };
}; };

View File

@ -76,3 +76,7 @@ export type TDecodedGcpIamAuthJwt = {
[key: string]: string; [key: string]: string;
}; };
}; };
export type TRevokeGcpAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@ -7,6 +7,7 @@ import { IdentityAuthMethod, SecretKeyEncoding, TIdentityKubernetesAuthsUpdate }
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { import {
decryptSymmetric, decryptSymmetric,
@ -16,11 +17,11 @@ import {
infisicalSymmetricDecrypt, infisicalSymmetricDecrypt,
infisicalSymmetricEncypt infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption"; } from "@app/lib/crypto/encryption";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
@ -32,13 +33,14 @@ import {
TCreateTokenReviewResponse, TCreateTokenReviewResponse,
TGetKubernetesAuthDTO, TGetKubernetesAuthDTO,
TLoginKubernetesAuthDTO, TLoginKubernetesAuthDTO,
TRevokeKubernetesAuthDTO,
TUpdateKubernetesAuthDTO TUpdateKubernetesAuthDTO
} from "./identity-kubernetes-auth-types"; } from "./identity-kubernetes-auth-types";
type TIdentityKubernetesAuthServiceFactoryDep = { type TIdentityKubernetesAuthServiceFactoryDep = {
identityKubernetesAuthDAL: Pick< identityKubernetesAuthDAL: Pick<
TIdentityKubernetesAuthDALFactory, TIdentityKubernetesAuthDALFactory,
"create" | "findOne" | "transaction" | "updateById" "create" | "findOne" | "transaction" | "updateById" | "delete"
>; >;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne" | "findById">; identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne" | "findById">;
@ -533,10 +535,54 @@ export const identityKubernetesAuthServiceFactory = ({
return { ...identityKubernetesAuth, caCert, tokenReviewerJwt, orgId: identityMembershipOrg.orgId }; return { ...identityKubernetesAuth, caCert, tokenReviewerJwt, orgId: identityMembershipOrg.orgId };
}; };
const revokeIdentityKubernetesAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeKubernetesAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.KUBERNETES_AUTH)
throw new BadRequestError({
message: "The identity does not have kubenetes auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to revoke kubenetes auth of identity with more privileged role"
});
const revokedIdentityKubernetesAuth = await identityKubernetesAuthDAL.transaction(async (tx) => {
const deletedKubernetesAuth = await identityKubernetesAuthDAL.delete({ identityId }, tx);
await identityDAL.updateById(identityId, { authMethod: null }, tx);
return { ...deletedKubernetesAuth?.[0], orgId: identityMembershipOrg.orgId };
});
return revokedIdentityKubernetesAuth;
};
return { return {
login, login,
attachKubernetesAuth, attachKubernetesAuth,
updateKubernetesAuth, updateKubernetesAuth,
getKubernetesAuth getKubernetesAuth,
revokeIdentityKubernetesAuth
}; };
}; };

View File

@ -59,3 +59,7 @@ export type TCreateTokenReviewResponse = {
}; };
status: TCreateTokenReviewSuccessResponse | TCreateTokenReviewErrorResponse; status: TCreateTokenReviewSuccessResponse | TCreateTokenReviewErrorResponse;
}; };
export type TRevokeKubernetesAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@ -12,7 +12,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
const findByProjectId = async (projectId: string, filter: { identityId?: string } = {}, tx?: Knex) => { const findByProjectId = async (projectId: string, filter: { identityId?: string } = {}, tx?: Knex) => {
try { try {
const docs = await (tx || db)(TableName.IdentityProjectMembership) const docs = await (tx || db.replicaNode())(TableName.IdentityProjectMembership)
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId) .where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`) .join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
.where((qb) => { .where((qb) => {

View File

@ -25,7 +25,9 @@ import {
TCreateUaClientSecretDTO, TCreateUaClientSecretDTO,
TGetUaClientSecretsDTO, TGetUaClientSecretsDTO,
TGetUaDTO, TGetUaDTO,
TGetUniversalAuthClientSecretByIdDTO,
TRevokeUaClientSecretDTO, TRevokeUaClientSecretDTO,
TRevokeUaDTO,
TUpdateUaDTO TUpdateUaDTO
} from "./identity-ua-types"; } from "./identity-ua-types";
@ -136,7 +138,7 @@ export const identityUaServiceFactory = ({
return { accessToken, identityUa, validClientSecretInfo, identityAccessToken, identityMembershipOrg }; return { accessToken, identityUa, validClientSecretInfo, identityAccessToken, identityMembershipOrg };
}; };
const attachUa = async ({ const attachUniversalAuth = async ({
accessTokenMaxTTL, accessTokenMaxTTL,
identityId, identityId,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
@ -227,7 +229,7 @@ export const identityUaServiceFactory = ({
return { ...identityUa, orgId: identityMembershipOrg.orgId }; return { ...identityUa, orgId: identityMembershipOrg.orgId };
}; };
const updateUa = async ({ const updateUniversalAuth = async ({
accessTokenMaxTTL, accessTokenMaxTTL,
identityId, identityId,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
@ -312,7 +314,7 @@ export const identityUaServiceFactory = ({
return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId }; return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId };
}; };
const getIdentityUa = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetUaDTO) => { const getIdentityUniversalAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetUaDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
@ -333,7 +335,50 @@ export const identityUaServiceFactory = ({
return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId }; return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId };
}; };
const createUaClientSecret = async ({ const revokeIdentityUniversalAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeUaDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to revoke universal auth of identity with more privileged role"
});
const revokedIdentityUniversalAuth = await identityUaDAL.transaction(async (tx) => {
const deletedUniversalAuth = await identityUaDAL.delete({ identityId }, tx);
await identityDAL.updateById(identityId, { authMethod: null }, tx);
return { ...deletedUniversalAuth?.[0], orgId: identityMembershipOrg.orgId };
});
return revokedIdentityUniversalAuth;
};
const createUniversalAuthClientSecret = async ({
actor, actor,
actorId, actorId,
actorOrgId, actorOrgId,
@ -396,7 +441,7 @@ export const identityUaServiceFactory = ({
}; };
}; };
const getUaClientSecrets = async ({ const getUniversalAuthClientSecrets = async ({
actor, actor,
actorId, actorId,
actorOrgId, actorOrgId,
@ -442,7 +487,47 @@ export const identityUaServiceFactory = ({
return { clientSecrets, orgId: identityMembershipOrg.orgId }; return { clientSecrets, orgId: identityMembershipOrg.orgId };
}; };
const revokeUaClientSecret = async ({ const getUniversalAuthClientSecretById = async ({
identityId,
actorId,
actor,
actorOrgId,
actorAuthMethod,
clientSecretId
}: TGetUniversalAuthClientSecretByIdDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to read identity client secret of project with more privileged role"
});
const clientSecret = await identityUaClientSecretDAL.findById(clientSecretId);
return { ...clientSecret, identityId, orgId: identityMembershipOrg.orgId };
};
const revokeUniversalAuthClientSecret = async ({
identityId, identityId,
actorId, actorId,
actor, actor,
@ -475,7 +560,7 @@ export const identityUaServiceFactory = ({
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge) if (!hasPriviledge)
throw new ForbiddenRequestError({ throw new ForbiddenRequestError({
message: "Failed to add identity to project with more privileged role" message: "Failed to revoke identity client secret with more privileged role"
}); });
const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, { const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, {
@ -486,11 +571,13 @@ export const identityUaServiceFactory = ({
return { return {
login, login,
attachUa, attachUniversalAuth,
updateUa, updateUniversalAuth,
getIdentityUa, getIdentityUniversalAuth,
createUaClientSecret, revokeIdentityUniversalAuth,
getUaClientSecrets, createUniversalAuthClientSecret,
revokeUaClientSecret getUniversalAuthClientSecrets,
revokeUniversalAuthClientSecret,
getUniversalAuthClientSecretById
}; };
}; };

View File

@ -22,6 +22,10 @@ export type TGetUaDTO = {
identityId: string; identityId: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TRevokeUaDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;
export type TCreateUaClientSecretDTO = { export type TCreateUaClientSecretDTO = {
identityId: string; identityId: string;
description: string; description: string;
@ -37,3 +41,8 @@ export type TRevokeUaClientSecretDTO = {
identityId: string; identityId: string;
clientSecretId: string; clientSecretId: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TGetUniversalAuthClientSecretByIdDTO = {
identityId: string;
clientSecretId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@ -12,7 +12,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
const findOne = async (filter: Partial<TIdentityOrgMemberships>, tx?: Knex) => { const findOne = async (filter: Partial<TIdentityOrgMemberships>, tx?: Knex) => {
try { try {
const [data] = await (tx || db)(TableName.IdentityOrgMembership) const [data] = await (tx || db.replicaNode())(TableName.IdentityOrgMembership)
.where(filter) .where(filter)
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`) .join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
.select(selectAllTableCols(TableName.IdentityOrgMembership)) .select(selectAllTableCols(TableName.IdentityOrgMembership))
@ -27,10 +27,10 @@ export const identityOrgDALFactory = (db: TDbClient) => {
} }
}; };
const findByOrgId = async (orgId: string, tx?: Knex) => { const find = async (filter: Partial<TIdentityOrgMemberships>, tx?: Knex) => {
try { try {
const docs = await (tx || db)(TableName.IdentityOrgMembership) const docs = await (tx || db.replicaNode())(TableName.IdentityOrgMembership)
.where(`${TableName.IdentityOrgMembership}.orgId`, orgId) .where(filter)
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`) .join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`) .leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
.select(selectAllTableCols(TableName.IdentityOrgMembership)) .select(selectAllTableCols(TableName.IdentityOrgMembership))
@ -79,5 +79,5 @@ export const identityOrgDALFactory = (db: TDbClient) => {
} }
}; };
return { ...identityOrgOrm, findOne, findByOrgId }; return { ...identityOrgOrm, find, findOne };
}; };

View File

@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas"; import { OrgMembershipRole, TableName, TOrgRoles } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { isAtLeastAsPrivileged } from "@app/lib/casl";
@ -10,12 +11,13 @@ import { TOrgPermission } from "@app/lib/types";
import { ActorType } from "../auth/auth-type"; import { ActorType } from "../auth/auth-type";
import { TIdentityDALFactory } from "./identity-dal"; import { TIdentityDALFactory } from "./identity-dal";
import { TIdentityOrgDALFactory } from "./identity-org-dal"; import { TIdentityOrgDALFactory } from "./identity-org-dal";
import { TCreateIdentityDTO, TDeleteIdentityDTO, TUpdateIdentityDTO } from "./identity-types"; import { TCreateIdentityDTO, TDeleteIdentityDTO, TGetIdentityByIdDTO, TUpdateIdentityDTO } from "./identity-types";
type TIdentityServiceFactoryDep = { type TIdentityServiceFactoryDep = {
identityDAL: TIdentityDALFactory; identityDAL: TIdentityDALFactory;
identityOrgMembershipDAL: TIdentityOrgDALFactory; identityOrgMembershipDAL: TIdentityOrgDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
}; };
export type TIdentityServiceFactory = ReturnType<typeof identityServiceFactory>; export type TIdentityServiceFactory = ReturnType<typeof identityServiceFactory>;
@ -23,7 +25,8 @@ export type TIdentityServiceFactory = ReturnType<typeof identityServiceFactory>;
export const identityServiceFactory = ({ export const identityServiceFactory = ({
identityDAL, identityDAL,
identityOrgMembershipDAL, identityOrgMembershipDAL,
permissionService permissionService,
licenseService
}: TIdentityServiceFactoryDep) => { }: TIdentityServiceFactoryDep) => {
const createIdentity = async ({ const createIdentity = async ({
name, name,
@ -45,6 +48,14 @@ export const identityServiceFactory = ({
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged identity" }); if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged identity" });
const plan = await licenseService.getPlan(orgId);
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
throw new BadRequestError({
message: "Failed to create identity due to identity limit reached. Upgrade plan to create more identities."
});
}
const identity = await identityDAL.transaction(async (tx) => { const identity = await identityDAL.transaction(async (tx) => {
const newIdentity = await identityDAL.create({ name }, tx); const newIdentity = await identityDAL.create({ name }, tx);
await identityOrgMembershipDAL.create( await identityOrgMembershipDAL.create(
@ -58,6 +69,7 @@ export const identityServiceFactory = ({
); );
return newIdentity; return newIdentity;
}); });
await licenseService.updateSubscriptionOrgMemberCount(orgId);
return identity; return identity;
}; };
@ -126,6 +138,24 @@ export const identityServiceFactory = ({
return { ...identity, orgId: identityOrgMembership.orgId }; return { ...identity, orgId: identityOrgMembership.orgId };
}; };
const getIdentityById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TGetIdentityByIdDTO) => {
const doc = await identityOrgMembershipDAL.find({
[`${TableName.IdentityOrgMembership}.identityId` as "identityId"]: id
});
const identity = doc[0];
if (!identity) throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identity.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
return identity;
};
const deleteIdentity = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteIdentityDTO) => { const deleteIdentity = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteIdentityDTO) => {
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id }); const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
@ -150,6 +180,9 @@ export const identityServiceFactory = ({
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" }); throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
const deletedIdentity = await identityDAL.deleteById(id); const deletedIdentity = await identityDAL.deleteById(id);
await licenseService.updateSubscriptionOrgMemberCount(identityOrgMembership.orgId);
return { ...deletedIdentity, orgId: identityOrgMembership.orgId }; return { ...deletedIdentity, orgId: identityOrgMembership.orgId };
}; };
@ -157,7 +190,9 @@ export const identityServiceFactory = ({
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
const identityMemberships = await identityOrgMembershipDAL.findByOrgId(orgId); const identityMemberships = await identityOrgMembershipDAL.find({
[`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId
});
return identityMemberships; return identityMemberships;
}; };
@ -165,6 +200,7 @@ export const identityServiceFactory = ({
createIdentity, createIdentity,
updateIdentity, updateIdentity,
deleteIdentity, deleteIdentity,
listOrgIdentities listOrgIdentities,
getIdentityById
}; };
}; };

View File

@ -16,6 +16,10 @@ export type TDeleteIdentityDTO = {
id: string; id: string;
} & Omit<TOrgPermission, "orgId">; } & Omit<TOrgPermission, "orgId">;
export type TGetIdentityByIdDTO = {
id: string;
} & Omit<TOrgPermission, "orgId">;
export interface TIdentityTrustedIp { export interface TIdentityTrustedIp {
ipAddress: string; ipAddress: string;
type: IPType; type: IPType;

View File

@ -178,7 +178,8 @@ export const integrationAuthServiceFactory = ({
actorAuthMethod, actorAuthMethod,
accessId, accessId,
namespace, namespace,
accessToken accessToken,
awsAssumeIamRoleArn
}: TSaveIntegrationAccessTokenDTO) => { }: TSaveIntegrationAccessTokenDTO) => {
if (!Object.values(Integrations).includes(integration as Integrations)) if (!Object.values(Integrations).includes(integration as Integrations))
throw new BadRequestError({ message: "Invalid integration" }); throw new BadRequestError({ message: "Invalid integration" });
@ -230,7 +231,7 @@ export const integrationAuthServiceFactory = ({
updateDoc.accessExpiresAt = tokenDetails.accessExpiresAt; updateDoc.accessExpiresAt = tokenDetails.accessExpiresAt;
} }
if (!refreshToken && (accessId || accessToken)) { if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) {
if (accessToken) { if (accessToken) {
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessToken, key); const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessToken, key);
updateDoc.accessIV = accessEncToken.iv; updateDoc.accessIV = accessEncToken.iv;
@ -243,6 +244,12 @@ export const integrationAuthServiceFactory = ({
updateDoc.accessIdTag = accessEncToken.tag; updateDoc.accessIdTag = accessEncToken.tag;
updateDoc.accessIdCiphertext = accessEncToken.ciphertext; updateDoc.accessIdCiphertext = accessEncToken.ciphertext;
} }
if (awsAssumeIamRoleArn) {
const awsAssumeIamRoleArnEnc = encryptSymmetric128BitHexKeyUTF8(awsAssumeIamRoleArn, key);
updateDoc.awsAssumeIamRoleArnCipherText = awsAssumeIamRoleArnEnc.ciphertext;
updateDoc.awsAssumeIamRoleArnIV = awsAssumeIamRoleArnEnc.iv;
updateDoc.awsAssumeIamRoleArnTag = awsAssumeIamRoleArnEnc.tag;
}
} }
return integrationAuthDAL.create(updateDoc); return integrationAuthDAL.create(updateDoc);
}; };
@ -251,6 +258,14 @@ export const integrationAuthServiceFactory = ({
const getIntegrationAccessToken = async (integrationAuth: TIntegrationAuths, botKey: string) => { const getIntegrationAccessToken = async (integrationAuth: TIntegrationAuths, botKey: string) => {
let accessToken: string | undefined; let accessToken: string | undefined;
let accessId: string | undefined; let accessId: string | undefined;
// this means its not access token based
if (
integrationAuth.integration === Integrations.AWS_SECRET_MANAGER &&
integrationAuth.awsAssumeIamRoleArnCipherText
) {
return { accessToken: "", accessId: "" };
}
if (integrationAuth.accessTag && integrationAuth.accessIV && integrationAuth.accessCiphertext) { if (integrationAuth.accessTag && integrationAuth.accessIV && integrationAuth.accessCiphertext) {
accessToken = decryptSymmetric128BitHexKeyUTF8({ accessToken = decryptSymmetric128BitHexKeyUTF8({
ciphertext: integrationAuth.accessCiphertext, ciphertext: integrationAuth.accessCiphertext,

View File

@ -17,6 +17,7 @@ export type TSaveIntegrationAccessTokenDTO = {
url?: string; url?: string;
namespace?: string; namespace?: string;
refreshToken?: string; refreshToken?: string;
awsAssumeIamRoleArn?: string;
} & TProjectPermission; } & TProjectPermission;
export type TDeleteIntegrationAuthsDTO = TProjectPermission & { export type TDeleteIntegrationAuthsDTO = TProjectPermission & {

View File

@ -17,14 +17,17 @@ import {
UntagResourceCommand, UntagResourceCommand,
UpdateSecretCommand UpdateSecretCommand
} from "@aws-sdk/client-secrets-manager"; } from "@aws-sdk/client-secrets-manager";
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import AWS, { AWSError } from "aws-sdk"; import AWS, { AWSError } from "aws-sdk";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { randomUUID } from "crypto";
import sodium from "libsodium-wrappers"; import sodium from "libsodium-wrappers";
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import { z } from "zod"; import { z } from "zod";
import { SecretType, TIntegrationAuths, TIntegrations, TSecrets } from "@app/db/schemas"; import { SecretType, TIntegrationAuths, TIntegrations, TSecrets } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
@ -257,14 +260,27 @@ const syncSecretsGCPSecretManager = async ({
const syncSecretsAzureKeyVault = async ({ const syncSecretsAzureKeyVault = async ({
integration, integration,
secrets, secrets,
accessToken accessToken,
createManySecretsRawFn,
updateManySecretsRawFn
}: { }: {
integration: TIntegrations; integration: TIntegrations & {
projectId: string;
environment: {
id: string;
name: string;
slug: string;
};
secretPath: string;
};
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessToken: string; accessToken: string;
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
updateManySecretsRawFn: (params: TUpdateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
}) => { }) => {
interface GetAzureKeyVaultSecret { interface GetAzureKeyVaultSecret {
id: string; // secret URI id: string; // secret URI
value: string;
attributes: { attributes: {
enabled: true; enabled: true;
created: number; created: number;
@ -361,6 +377,83 @@ const syncSecretsAzureKeyVault = async ({
} }
}); });
const secretsToAdd: { [key: string]: string } = {};
const secretsToUpdate: { [key: string]: string } = {};
const secretKeysToRemoveFromDelete = new Set<string>();
const metadata = IntegrationMetadataSchema.parse(integration.metadata);
if (!integration.lastUsed) {
Object.keys(res).forEach((key) => {
// first time using integration
const underscoredKey = key.replace(/-/g, "_");
// -> apply initial sync behavior
switch (metadata.initialSyncBehavior) {
case IntegrationInitialSyncBehavior.PREFER_TARGET: {
if (!(underscoredKey in secrets)) {
secretsToAdd[underscoredKey] = res[key].value;
setSecrets.push({
key,
value: res[key].value
});
} else if (secrets[underscoredKey]?.value !== res[key].value) {
secretsToUpdate[underscoredKey] = res[key].value;
const toEditSecretIndex = setSecrets.findIndex((secret) => secret.key === key);
if (toEditSecretIndex >= 0) {
setSecrets[toEditSecretIndex].value = res[key].value;
}
}
secretKeysToRemoveFromDelete.add(key);
break;
}
case IntegrationInitialSyncBehavior.PREFER_SOURCE: {
if (!(underscoredKey in secrets)) {
secretsToAdd[underscoredKey] = res[key].value;
setSecrets.push({
key,
value: res[key].value
});
}
secretKeysToRemoveFromDelete.add(key);
break;
}
default:
break;
}
});
}
if (Object.keys(secretsToUpdate).length) {
await updateManySecretsRawFn({
projectId: integration.projectId,
environment: integration.environment.slug,
path: integration.secretPath,
secrets: Object.keys(secretsToUpdate).map((key) => ({
secretName: key,
secretValue: secretsToUpdate[key],
type: SecretType.Shared,
secretComment: ""
}))
});
}
if (Object.keys(secretsToAdd).length) {
await createManySecretsRawFn({
projectId: integration.projectId,
environment: integration.environment.slug,
path: integration.secretPath,
secrets: Object.keys(secretsToAdd).map((key) => ({
secretName: key,
secretValue: secretsToAdd[key],
type: SecretType.Shared,
secretComment: ""
}))
});
}
const setSecretAzureKeyVault = async ({ const setSecretAzureKeyVault = async ({
key, key,
value, value,
@ -428,7 +521,7 @@ const syncSecretsAzureKeyVault = async ({
}); });
} }
for await (const deleteSecret of deleteSecrets) { for await (const deleteSecret of deleteSecrets.filter((secret) => !secretKeysToRemoveFromDelete.has(secret.key))) {
const { key } = deleteSecret; const { key } = deleteSecret;
await request.delete(`${integration.app}/secrets/${key}?api-version=7.3`, { await request.delete(`${integration.app}/secrets/${key}?api-version=7.3`, {
headers: { headers: {
@ -605,24 +698,61 @@ const syncSecretsAWSSecretManager = async ({
integration, integration,
secrets, secrets,
accessId, accessId,
accessToken accessToken,
awsAssumeRoleArn,
projectId
}: { }: {
integration: TIntegrations; integration: TIntegrations;
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessId: string | null; accessId: string | null;
accessToken: string; accessToken: string;
awsAssumeRoleArn: string | null;
projectId?: string;
}) => { }) => {
const appCfg = getConfig();
const metadata = z.record(z.any()).parse(integration.metadata || {}); const metadata = z.record(z.any()).parse(integration.metadata || {});
if (!accessId) { if (!accessId && !awsAssumeRoleArn) {
throw new Error("AWS access ID is required"); throw new Error("AWS access ID/AWS Assume Role is required");
}
let accessKeyId = "";
let secretAccessKey = "";
let sessionToken;
if (awsAssumeRoleArn) {
const client = new STSClient({
region: integration.region as string,
credentials:
appCfg.CLIENT_ID_AWS_INTEGRATION && appCfg.CLIENT_SECRET_AWS_INTEGRATION
? {
accessKeyId: appCfg.CLIENT_ID_AWS_INTEGRATION,
secretAccessKey: appCfg.CLIENT_SECRET_AWS_INTEGRATION
}
: undefined
});
const command = new AssumeRoleCommand({
RoleArn: awsAssumeRoleArn,
RoleSessionName: `infisical-sm-${randomUUID()}`,
DurationSeconds: 900, // 15mins
ExternalId: projectId
});
const response = await client.send(command);
if (!response.Credentials?.AccessKeyId || !response.Credentials?.SecretAccessKey)
throw new Error("Failed to assume role");
accessKeyId = response.Credentials?.AccessKeyId;
secretAccessKey = response.Credentials?.SecretAccessKey;
sessionToken = response.Credentials?.SessionToken;
} else {
accessKeyId = accessId as string;
secretAccessKey = accessToken;
} }
const secretsManager = new SecretsManagerClient({ const secretsManager = new SecretsManagerClient({
region: integration.region as string, region: integration.region as string,
credentials: { credentials: {
accessKeyId: accessId, accessKeyId,
secretAccessKey: accessToken secretAccessKey,
sessionToken
} }
}); });
@ -3478,7 +3608,9 @@ export const syncIntegrationSecrets = async ({
secrets, secrets,
accessId, accessId,
accessToken, accessToken,
appendices awsAssumeRoleArn,
appendices,
projectId
}: { }: {
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>; createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
updateManySecretsRawFn: (params: TUpdateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>; updateManySecretsRawFn: (params: TUpdateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
@ -3495,8 +3627,10 @@ export const syncIntegrationSecrets = async ({
integrationAuth: TIntegrationAuths; integrationAuth: TIntegrationAuths;
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessId: string | null; accessId: string | null;
awsAssumeRoleArn: string | null;
accessToken: string; accessToken: string;
appendices?: { prefix: string; suffix: string }; appendices?: { prefix: string; suffix: string };
projectId?: string;
}) => { }) => {
let response: { isSynced: boolean; syncMessage: string } | null = null; let response: { isSynced: boolean; syncMessage: string } | null = null;
@ -3512,7 +3646,9 @@ export const syncIntegrationSecrets = async ({
await syncSecretsAzureKeyVault({ await syncSecretsAzureKeyVault({
integration, integration,
secrets, secrets,
accessToken accessToken,
createManySecretsRawFn,
updateManySecretsRawFn
}); });
break; break;
case Integrations.AWS_PARAMETER_STORE: case Integrations.AWS_PARAMETER_STORE:
@ -3528,7 +3664,9 @@ export const syncIntegrationSecrets = async ({
integration, integration,
secrets, secrets,
accessId, accessId,
accessToken accessToken,
awsAssumeRoleArn,
projectId
}); });
break; break;
case Integrations.HEROKU: case Integrations.HEROKU:

View File

@ -22,7 +22,7 @@ export const integrationDALFactory = (db: TDbClient) => {
const find = async (filter: Partial<TIntegrations>, tx?: Knex) => { const find = async (filter: Partial<TIntegrations>, tx?: Knex) => {
try { try {
const docs = await integrationFindQuery(tx || db, filter); const docs = await integrationFindQuery(tx || db.replicaNode(), filter);
return docs.map(({ envId, envSlug, envName, ...el }) => ({ return docs.map(({ envId, envSlug, envName, ...el }) => ({
...el, ...el,
environment: { environment: {
@ -38,7 +38,7 @@ export const integrationDALFactory = (db: TDbClient) => {
const findOne = async (filter: Partial<TIntegrations>, tx?: Knex) => { const findOne = async (filter: Partial<TIntegrations>, tx?: Knex) => {
try { try {
const doc = await integrationFindQuery(tx || db, filter).first(); const doc = await integrationFindQuery(tx || db.replicaNode(), filter).first();
if (!doc) return; if (!doc) return;
const { envName: name, envSlug: slug, envId: id, ...el } = doc; const { envName: name, envSlug: slug, envId: id, ...el } = doc;
@ -50,7 +50,7 @@ export const integrationDALFactory = (db: TDbClient) => {
const findById = async (id: string, tx?: Knex) => { const findById = async (id: string, tx?: Knex) => {
try { try {
const doc = await integrationFindQuery(tx || db, { const doc = await integrationFindQuery(tx || db.replicaNode(), {
[`${TableName.Integration}.id` as "id"]: id [`${TableName.Integration}.id` as "id"]: id
}).first(); }).first();
if (!doc) return; if (!doc) return;
@ -64,7 +64,7 @@ export const integrationDALFactory = (db: TDbClient) => {
const findByProjectId = async (projectId: string, tx?: Knex) => { const findByProjectId = async (projectId: string, tx?: Knex) => {
try { try {
const integrations = await (tx || db)(TableName.Integration) const integrations = await (tx || db.replicaNode())(TableName.Integration)
.where(`${TableName.Environment}.projectId`, projectId) .where(`${TableName.Environment}.projectId`, projectId)
.join(TableName.Environment, `${TableName.Integration}.envId`, `${TableName.Environment}.id`) .join(TableName.Environment, `${TableName.Integration}.envId`, `${TableName.Environment}.id`)
.select(db.ref("name").withSchema(TableName.Environment).as("envName")) .select(db.ref("name").withSchema(TableName.Environment).as("envName"))
@ -90,7 +90,7 @@ export const integrationDALFactory = (db: TDbClient) => {
// used for syncing secrets // used for syncing secrets
// this will populate integration auth also // this will populate integration auth also
const findByProjectIdV2 = async (projectId: string, environment: string, tx?: Knex) => { const findByProjectIdV2 = async (projectId: string, environment: string, tx?: Knex) => {
const docs = await (tx || db)(TableName.Integration) const docs = await (tx || db.replicaNode())(TableName.Integration)
.where(`${TableName.Environment}.projectId`, projectId) .where(`${TableName.Environment}.projectId`, projectId)
.where("isActive", true) .where("isActive", true)
.where(`${TableName.Environment}.slug`, environment) .where(`${TableName.Environment}.slug`, environment)
@ -120,7 +120,10 @@ export const integrationDALFactory = (db: TDbClient) => {
db.ref("accessExpiresAt").withSchema(TableName.IntegrationAuth).as("accessExpiresAtAu"), db.ref("accessExpiresAt").withSchema(TableName.IntegrationAuth).as("accessExpiresAtAu"),
db.ref("metadata").withSchema(TableName.IntegrationAuth).as("metadataAu"), db.ref("metadata").withSchema(TableName.IntegrationAuth).as("metadataAu"),
db.ref("algorithm").withSchema(TableName.IntegrationAuth).as("algorithmAu"), db.ref("algorithm").withSchema(TableName.IntegrationAuth).as("algorithmAu"),
db.ref("keyEncoding").withSchema(TableName.IntegrationAuth).as("keyEncodingAu") db.ref("keyEncoding").withSchema(TableName.IntegrationAuth).as("keyEncodingAu"),
db.ref("awsAssumeIamRoleArnCipherText").withSchema(TableName.IntegrationAuth),
db.ref("awsAssumeIamRoleArnIV").withSchema(TableName.IntegrationAuth),
db.ref("awsAssumeIamRoleArnTag").withSchema(TableName.IntegrationAuth)
); );
return docs.map( return docs.map(
({ ({
@ -146,6 +149,9 @@ export const integrationDALFactory = (db: TDbClient) => {
algorithmAu: algorithm, algorithmAu: algorithm,
keyEncodingAu: keyEncoding, keyEncodingAu: keyEncoding,
accessExpiresAtAu: accessExpiresAt, accessExpiresAtAu: accessExpiresAt,
awsAssumeIamRoleArnIV,
awsAssumeIamRoleArnCipherText,
awsAssumeIamRoleArnTag,
...el ...el
}) => ({ }) => ({
...el, ...el,
@ -174,7 +180,10 @@ export const integrationDALFactory = (db: TDbClient) => {
metadata, metadata,
algorithm, algorithm,
keyEncoding, keyEncoding,
accessExpiresAt accessExpiresAt,
awsAssumeIamRoleArnIV,
awsAssumeIamRoleArnCipherText,
awsAssumeIamRoleArnTag
} }
}) })
); );

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