mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-11 12:11:38 +00:00
Compare commits
169 Commits
daniel/sdk
...
misc/add-c
Author | SHA1 | Date | |
---|---|---|---|
030d4fe152 | |||
0b8f6878fe | |||
0bb2b2887b | |||
eeb0111bbe | |||
d12c538511 | |||
6f67346b2a | |||
a93db44bbd | |||
1ddacfda62 | |||
351d0d0662 | |||
1859557f90 | |||
1b2a1f2339 | |||
15b4c397ab | |||
fc27ad4575 | |||
b7467a83ab | |||
3baf434230 | |||
e28471a9f4 | |||
b2d6563994 | |||
3537a5eb9b | |||
d5b17a8f24 | |||
7cdc47cd3a | |||
d666d60f9f | |||
491c4259ca | |||
cff20eb621 | |||
84d8879177 | |||
aa4f2adbb6 | |||
86ed3ef6d6 | |||
a5bb80adc4 | |||
0e87dd3996 | |||
e1801e9eb4 | |||
8eea82a1a0 | |||
694d0e3ed3 | |||
58f6c6b409 | |||
f4a33caba6 | |||
e0a6f09b5e | |||
1e701687ae | |||
15758b91f8 | |||
2d3a4a7559 | |||
a1d01d5cbd | |||
2e3aedc62b | |||
e0a5b1444a | |||
1c2698f533 | |||
b50833bded | |||
e0c774c045 | |||
514df55d67 | |||
311b378f3b | |||
b01b4323ca | |||
285a01af51 | |||
f7e658e62b | |||
a8aef2934a | |||
cc30476f79 | |||
5d59fe8810 | |||
90eed8d39b | |||
f5974ce9ad | |||
c6b51af4b1 | |||
5139bf2385 | |||
c13c37fc77 | |||
259c01c110 | |||
a016d0d33f | |||
663be06d30 | |||
fa392382da | |||
d34b2669c5 | |||
11ea5990c9 | |||
9a66514178 | |||
d4f9faf24d | |||
a3c8d06845 | |||
71b7be4057 | |||
5079a5889a | |||
232b375f46 | |||
d2acedf79e | |||
9d846319b0 | |||
d69267a3ca | |||
051eee8701 | |||
b5aa650899 | |||
376e185e2b | |||
a15a0a257c | |||
6facce220c | |||
620a423cee | |||
361496c644 | |||
e03f77d9cf | |||
60cb420242 | |||
1b8a77f507 | |||
5a957514df | |||
a6865585f3 | |||
1aaca12781 | |||
7ab5c02000 | |||
c735beea32 | |||
2d98560255 | |||
91bdd7ea6a | |||
b0f3476e4a | |||
14751df9de | |||
e1a4185f76 | |||
4905ad1f48 | |||
56bc25025a | |||
45da563465 | |||
1930d40be8 | |||
30b8d59796 | |||
aa6cca738e | |||
04dee70a55 | |||
dfb53dd333 | |||
ab19e7df6d | |||
f9a1accf84 | |||
ca86f3d2b6 | |||
de466b4b86 | |||
745f1c4e12 | |||
106dc261de | |||
548a0aed2a | |||
6029eaa9df | |||
8703314c0c | |||
00617ea7e8 | |||
084fc7c99e | |||
b6cc17d62a | |||
bd0d0bd333 | |||
c426ba517a | |||
973403c7f9 | |||
52fcf53d0e | |||
91634fbe76 | |||
f31340cf53 | |||
193d6dad54 | |||
0f36fc46b3 | |||
4072a40fe9 | |||
0dc132dda3 | |||
605ccb13e9 | |||
4a1a399fd8 | |||
d19e2f64f0 | |||
1e0f54d9a4 | |||
8d55c2802e | |||
e9639df8ce | |||
e0f5ecbe7b | |||
2160c66e20 | |||
1c5c7c75c4 | |||
3e230555fb | |||
24c75c6325 | |||
0a22a2a9ef | |||
d0f1cad98c | |||
ad92565783 | |||
6c98c96a15 | |||
f0a70d8769 | |||
d64e2fa243 | |||
afee158b95 | |||
ecca6f4db5 | |||
b198f97930 | |||
63a9e46936 | |||
7c067551a4 | |||
1193ddbed1 | |||
6457c34712 | |||
6a83b58de4 | |||
0100ddfb99 | |||
2bc6db1c47 | |||
92f2f16656 | |||
18e69578f0 | |||
0685a5ea8b | |||
bdc7c018eb | |||
bcd65333c0 | |||
371b96a13a | |||
c5c00b520c | |||
8de4443be1 | |||
96ad3b0264 | |||
df51d05c46 | |||
4f2f7b2f70 | |||
d79ffbe37e | |||
2c237ee277 | |||
56cc248425 | |||
61fcb2b605 | |||
66e5edcfc0 | |||
f9a9599659 | |||
637b0b955f | |||
092665737f | |||
f83c2215a5 | |||
0f41590d6a |
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
1189
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||||
|
2
backend/src/@types/fastify.d.ts
vendored
2
backend/src/@types/fastify.d.ts
vendored
@ -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;
|
||||||
|
241
backend/src/@types/knex.d.ts
vendored
241
backend/src/@types/knex.d.ts
vendored
@ -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
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
49
backend/src/db/migrations/20240624161942_add-oidc-auth.ts
Normal file
49
backend/src/db/migrations/20240624161942_add-oidc-auth.ts
Normal 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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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>;
|
||||||
|
@ -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";
|
||||||
|
@ -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>;
|
||||||
|
@ -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>;
|
||||||
|
@ -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",
|
||||||
|
34
backend/src/db/schemas/oidc-configs.ts
Normal file
34
backend/src/db/schemas/oidc-configs.ts
Normal 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>>;
|
@ -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>;
|
||||||
|
@ -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>;
|
||||||
|
@ -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" });
|
||||||
|
@ -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(),
|
||||||
|
355
backend/src/ee/routes/v1/oidc-router.ts
Normal file
355
backend/src/ee/routes/v1/oidc-router.ts
Normal 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -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",
|
||||||
|
@ -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`,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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])
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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" });
|
||||||
|
@ -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;
|
||||||
|
@ -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))
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
11
backend/src/ee/services/oidc/oidc-config-dal.ts
Normal file
11
backend/src/ee/services/oidc/oidc-config-dal.ts
Normal 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 };
|
||||||
|
};
|
637
backend/src/ee/services/oidc/oidc-config-service.ts
Normal file
637
backend/src/ee/services/oidc/oidc-config-service.ts
Normal 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 };
|
||||||
|
};
|
56
backend/src/ee/services/oidc/oidc-config-types.ts
Normal file
56
backend/src/ee/services/oidc/oidc-config-types.ts
Normal 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;
|
@ -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);
|
||||||
|
@ -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`,
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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",
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
|
@ -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"),
|
||||||
|
@ -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",
|
||||||
|
@ -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),
|
||||||
|
@ -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) {
|
||||||
|
@ -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());
|
||||||
|
@ -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,
|
||||||
|
@ -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()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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")
|
||||||
|
@ -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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -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,14 +372,15 @@ 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 } =
|
||||||
actor: req.permission.type,
|
await server.services.identityUa.createUniversalAuthClientSecret({
|
||||||
actorId: req.permission.id,
|
actor: req.permission.type,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
identityId: req.params.identityId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body
|
identityId: req.params.identityId,
|
||||||
});
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
@ -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,
|
{
|
||||||
actorId: req.permission.id,
|
actor: req.permission.type,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
identityId: req.params.identityId
|
actorOrgId: req.permission.orgId,
|
||||||
});
|
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,
|
@ -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";
|
||||||
|
@ -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)
|
||||||
|
@ -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",
|
||||||
|
@ -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,
|
||||||
|
@ -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 });
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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) => {
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -52,3 +52,7 @@ export type TGetCallerIdentityResponse = {
|
|||||||
ResponseMetadata: { RequestId: string };
|
ResponseMetadata: { RequestId: string };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRevokeAwsAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -118,3 +118,7 @@ export type TDecodedAzureAuthJwt = {
|
|||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRevokeAzureAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -76,3 +76,7 @@ export type TDecodedGcpIamAuthJwt = {
|
|||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRevokeGcpAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -59,3 +59,7 @@ export type TCreateTokenReviewResponse = {
|
|||||||
};
|
};
|
||||||
status: TCreateTokenReviewSuccessResponse | TCreateTokenReviewErrorResponse;
|
status: TCreateTokenReviewSuccessResponse | TCreateTokenReviewErrorResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRevokeKubernetesAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -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) => {
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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">;
|
||||||
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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 & {
|
||||||
|
@ -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:
|
||||||
|
@ -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
Reference in New Issue
Block a user