mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 21:21:29 +00:00
Merge branch 'main' into feature/ldap-gateway-support
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { OnePassConnectionMethod } from "./1password-connection-enums";
|
||||
|
||||
export const OnePassConnectionAccessTokenCredentialsSchema = z.object({
|
||||
@@ -33,7 +34,7 @@ export const SanitizedOnePassConnectionSchema = z.discriminatedUnion("method", [
|
||||
credentials: OnePassConnectionAccessTokenCredentialsSchema.pick({
|
||||
instanceUrl: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.OnePass]} (API Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateOnePassConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -57,8 +58,10 @@ export const UpdateOnePassConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.OnePass));
|
||||
|
||||
export const OnePassConnectionListItemSchema = z.object({
|
||||
name: z.literal("1Password"),
|
||||
app: z.literal(AppConnection.OnePass),
|
||||
methods: z.nativeEnum(OnePassConnectionMethod).array()
|
||||
});
|
||||
export const OnePassConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("1Password"),
|
||||
app: z.literal(AppConnection.OnePass),
|
||||
methods: z.nativeEnum(OnePassConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.OnePass] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { Auth0ConnectionMethod } from "./auth0-connection-enums";
|
||||
|
||||
export const Auth0ConnectionClientCredentialsInputCredentialsSchema = z.object({
|
||||
@@ -59,7 +60,7 @@ export const SanitizedAuth0ConnectionSchema = z.discriminatedUnion("method", [
|
||||
clientId: true,
|
||||
audience: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Auth0]} (Client Credentials)` }))
|
||||
]);
|
||||
|
||||
export const ValidateAuth0ConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -85,10 +86,12 @@ export const UpdateAuth0ConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Auth0));
|
||||
|
||||
export const Auth0ConnectionListItemSchema = z.object({
|
||||
name: z.literal("Auth0"),
|
||||
app: z.literal(AppConnection.Auth0),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||
methods: z.nativeEnum(Auth0ConnectionMethod).array()
|
||||
});
|
||||
export const Auth0ConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Auth0"),
|
||||
app: z.literal(AppConnection.Auth0),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||
methods: z.nativeEnum(Auth0ConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Auth0] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { AwsConnectionMethod } from "./aws-connection-enums";
|
||||
|
||||
export const AwsConnectionAssumeRoleCredentialsSchema = z.object({
|
||||
@@ -39,11 +40,11 @@ export const SanitizedAwsConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseAwsConnectionSchema.extend({
|
||||
method: z.literal(AwsConnectionMethod.AssumeRole),
|
||||
credentials: AwsConnectionAssumeRoleCredentialsSchema.pick({})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AWS]} (Assume Role)` })),
|
||||
BaseAwsConnectionSchema.extend({
|
||||
method: z.literal(AwsConnectionMethod.AccessKey),
|
||||
credentials: AwsConnectionAccessTokenCredentialsSchema.pick({ accessKeyId: true })
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AWS]} (Access Key)` }))
|
||||
]);
|
||||
|
||||
export const ValidateAwsConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -72,11 +73,13 @@ export const UpdateAwsConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AWS));
|
||||
|
||||
export const AwsConnectionListItemSchema = z.object({
|
||||
name: z.literal("AWS"),
|
||||
app: z.literal(AppConnection.AWS),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(AwsConnectionMethod.AssumeRole), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||
methods: z.nativeEnum(AwsConnectionMethod).array(),
|
||||
accessKeyId: z.string().optional()
|
||||
});
|
||||
export const AwsConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("AWS"),
|
||||
app: z.literal(AppConnection.AWS),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(AwsConnectionMethod.AssumeRole), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||
methods: z.nativeEnum(AwsConnectionMethod).array(),
|
||||
accessKeyId: z.string().optional()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AWS] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { AzureADCSConnectionMethod } from "./azure-adcs-connection-enums";
|
||||
|
||||
export const AzureADCSUsernamePasswordCredentialsSchema = z.object({
|
||||
@@ -55,7 +56,7 @@ export const SanitizedAzureADCSConnectionSchema = z.discriminatedUnion("method",
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureADCS]} (Username and Password)` }))
|
||||
]);
|
||||
|
||||
export const ValidateAzureADCSConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -81,8 +82,10 @@ export const UpdateAzureADCSConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureADCS));
|
||||
|
||||
export const AzureADCSConnectionListItemSchema = z.object({
|
||||
name: z.literal("Azure ADCS"),
|
||||
app: z.literal(AppConnection.AzureADCS),
|
||||
methods: z.nativeEnum(AzureADCSConnectionMethod).array()
|
||||
});
|
||||
export const AzureADCSConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Azure ADCS"),
|
||||
app: z.literal(AppConnection.AzureADCS),
|
||||
methods: z.nativeEnum(AzureADCSConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AzureADCS] }));
|
||||
|
||||
+13
-8
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { AzureAppConfigurationConnectionMethod } from "./azure-app-configuration-connection-enums";
|
||||
|
||||
export const AzureAppConfigurationConnectionOAuthInputCredentialsSchema = z.object({
|
||||
@@ -104,19 +105,23 @@ export const SanitizedAzureAppConfigurationConnectionSchema = z.discriminatedUni
|
||||
credentials: AzureAppConfigurationConnectionOAuthOutputCredentialsSchema.pick({
|
||||
tenantId: true
|
||||
})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureAppConfiguration]} (OAuth)` })),
|
||||
BaseAzureAppConfigurationConnectionSchema.extend({
|
||||
method: z.literal(AzureAppConfigurationConnectionMethod.ClientSecret),
|
||||
credentials: AzureAppConfigurationConnectionClientSecretOutputCredentialsSchema.pick({
|
||||
clientId: true,
|
||||
tenantId: true
|
||||
})
|
||||
})
|
||||
}).describe(
|
||||
JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureAppConfiguration]} (Client Secret)` })
|
||||
)
|
||||
]);
|
||||
|
||||
export const AzureAppConfigurationConnectionListItemSchema = z.object({
|
||||
name: z.literal("Azure App Configuration"),
|
||||
app: z.literal(AppConnection.AzureAppConfiguration),
|
||||
methods: z.nativeEnum(AzureAppConfigurationConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
});
|
||||
export const AzureAppConfigurationConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Azure App Configuration"),
|
||||
app: z.literal(AppConnection.AzureAppConfiguration),
|
||||
methods: z.nativeEnum(AzureAppConfigurationConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AzureAppConfiguration] }));
|
||||
|
||||
+14
-9
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums";
|
||||
|
||||
export const AzureClientSecretsConnectionOAuthInputCredentialsSchema = z.object({
|
||||
@@ -162,26 +163,30 @@ export const SanitizedAzureClientSecretsConnectionSchema = z.discriminatedUnion(
|
||||
credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema.pick({
|
||||
tenantId: true
|
||||
})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureClientSecrets]} (OAuth)` })),
|
||||
BaseAzureClientSecretsConnectionSchema.extend({
|
||||
method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret),
|
||||
credentials: AzureClientSecretsConnectionClientSecretOutputCredentialsSchema.pick({
|
||||
clientId: true,
|
||||
tenantId: true
|
||||
})
|
||||
}),
|
||||
}).describe(
|
||||
JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureClientSecrets]} (Client Secret)` })
|
||||
),
|
||||
BaseAzureClientSecretsConnectionSchema.extend({
|
||||
method: z.literal(AzureClientSecretsConnectionMethod.Certificate),
|
||||
credentials: AzureClientSecretsConnectionCertificateOutputCredentialsSchema.pick({
|
||||
tenantId: true,
|
||||
clientId: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureClientSecrets]} (Certificate)` }))
|
||||
]);
|
||||
|
||||
export const AzureClientSecretsConnectionListItemSchema = z.object({
|
||||
name: z.literal("Azure Client Secrets"),
|
||||
app: z.literal(AppConnection.AzureClientSecrets),
|
||||
methods: z.nativeEnum(AzureClientSecretsConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
});
|
||||
export const AzureClientSecretsConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Azure Client Secrets"),
|
||||
app: z.literal(AppConnection.AzureClientSecrets),
|
||||
methods: z.nativeEnum(AzureClientSecretsConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AzureClientSecrets] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { AzureDevOpsConnectionMethod } from "./azure-devops-enums";
|
||||
|
||||
export const AzureDevOpsConnectionOAuthInputCredentialsSchema = z.object({
|
||||
@@ -147,13 +148,13 @@ export const SanitizedAzureDevOpsConnectionSchema = z.discriminatedUnion("method
|
||||
tenantId: true,
|
||||
orgName: true
|
||||
})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureDevOps]} (OAuth)` })),
|
||||
BaseAzureDevOpsConnectionSchema.extend({
|
||||
method: z.literal(AzureDevOpsConnectionMethod.AccessToken),
|
||||
credentials: AzureDevOpsConnectionAccessTokenOutputCredentialsSchema.pick({
|
||||
orgName: true
|
||||
})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureDevOps]} (Access Token)` })),
|
||||
BaseAzureDevOpsConnectionSchema.extend({
|
||||
method: z.literal(AzureDevOpsConnectionMethod.ClientSecret),
|
||||
credentials: AzureDevOpsConnectionClientSecretOutputCredentialsSchema.pick({
|
||||
@@ -161,12 +162,14 @@ export const SanitizedAzureDevOpsConnectionSchema = z.discriminatedUnion("method
|
||||
tenantId: true,
|
||||
orgName: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureDevOps]} (Client Secret)` }))
|
||||
]);
|
||||
|
||||
export const AzureDevOpsConnectionListItemSchema = z.object({
|
||||
name: z.literal("Azure DevOps"),
|
||||
app: z.literal(AppConnection.AzureDevOps),
|
||||
methods: z.nativeEnum(AzureDevOpsConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
});
|
||||
export const AzureDevOpsConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Azure DevOps"),
|
||||
app: z.literal(AppConnection.AzureDevOps),
|
||||
methods: z.nativeEnum(AzureDevOpsConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AzureDevOps] }));
|
||||
|
||||
+11
-8
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { AzureKeyVaultConnectionMethod } from "./azure-key-vault-connection-enums";
|
||||
|
||||
export const AzureKeyVaultConnectionOAuthInputCredentialsSchema = z.object({
|
||||
@@ -104,19 +105,21 @@ export const SanitizedAzureKeyVaultConnectionSchema = z.discriminatedUnion("meth
|
||||
credentials: AzureKeyVaultConnectionOAuthOutputCredentialsSchema.pick({
|
||||
tenantId: true
|
||||
})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureKeyVault]} (OAuth)` })),
|
||||
BaseAzureKeyVaultConnectionSchema.extend({
|
||||
method: z.literal(AzureKeyVaultConnectionMethod.ClientSecret),
|
||||
credentials: AzureKeyVaultConnectionClientSecretOutputCredentialsSchema.pick({
|
||||
clientId: true,
|
||||
tenantId: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureKeyVault]} (Client Secret)` }))
|
||||
]);
|
||||
|
||||
export const AzureKeyVaultConnectionListItemSchema = z.object({
|
||||
name: z.literal("Azure Key Vault"),
|
||||
app: z.literal(AppConnection.AzureKeyVault),
|
||||
methods: z.nativeEnum(AzureKeyVaultConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
});
|
||||
export const AzureKeyVaultConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Azure Key Vault"),
|
||||
app: z.literal(AppConnection.AzureKeyVault),
|
||||
methods: z.nativeEnum(AzureKeyVaultConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AzureKeyVault] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { BitbucketConnectionMethod } from "./bitbucket-connection-enums";
|
||||
|
||||
export const BitbucketConnectionAccessTokenCredentialsSchema = z.object({
|
||||
@@ -39,7 +40,7 @@ export const SanitizedBitbucketConnectionSchema = z.discriminatedUnion("method",
|
||||
credentials: BitbucketConnectionAccessTokenCredentialsSchema.pick({
|
||||
email: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Bitbucket]} (API Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateBitbucketConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -65,8 +66,10 @@ export const UpdateBitbucketConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Bitbucket));
|
||||
|
||||
export const BitbucketConnectionListItemSchema = z.object({
|
||||
name: z.literal("Bitbucket"),
|
||||
app: z.literal(AppConnection.Bitbucket),
|
||||
methods: z.nativeEnum(BitbucketConnectionMethod).array()
|
||||
});
|
||||
export const BitbucketConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Bitbucket"),
|
||||
app: z.literal(AppConnection.Bitbucket),
|
||||
methods: z.nativeEnum(BitbucketConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Bitbucket] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { CamundaConnectionMethod } from "./camunda-connection-enums";
|
||||
|
||||
const BaseCamundaConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Camunda) });
|
||||
@@ -44,7 +45,7 @@ export const SanitizedCamundaConnectionSchema = z.discriminatedUnion("method", [
|
||||
credentials: CamundaConnectionClientCredentialsOutputCredentialsSchema.pick({
|
||||
clientId: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Camunda]} (Client Credentials)` }))
|
||||
]);
|
||||
|
||||
export const ValidateCamundaConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -70,8 +71,10 @@ export const UpdateCamundaConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Camunda));
|
||||
|
||||
export const CamundaConnectionListItemSchema = z.object({
|
||||
name: z.literal("Camunda"),
|
||||
app: z.literal(AppConnection.Camunda),
|
||||
methods: z.nativeEnum(CamundaConnectionMethod).array()
|
||||
});
|
||||
export const CamundaConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Camunda"),
|
||||
app: z.literal(AppConnection.Camunda),
|
||||
methods: z.nativeEnum(CamundaConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Camunda] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { ChecklyConnectionMethod } from "./checkly-connection-constants";
|
||||
|
||||
export const ChecklyConnectionMethodSchema = z
|
||||
@@ -31,7 +32,7 @@ export const SanitizedChecklyConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseChecklyConnectionSchema.extend({
|
||||
method: ChecklyConnectionMethodSchema,
|
||||
credentials: ChecklyConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Checkly]} (Access Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateChecklyConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -55,8 +56,10 @@ export const UpdateChecklyConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Checkly));
|
||||
|
||||
export const ChecklyConnectionListItemSchema = z.object({
|
||||
name: z.literal("Checkly"),
|
||||
app: z.literal(AppConnection.Checkly),
|
||||
methods: z.nativeEnum(ChecklyConnectionMethod).array()
|
||||
});
|
||||
export const ChecklyConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Checkly"),
|
||||
app: z.literal(AppConnection.Checkly),
|
||||
methods: z.nativeEnum(ChecklyConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Checkly] }));
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { CloudflareConnectionMethod } from "./cloudflare-connection-enum";
|
||||
|
||||
const accountIdCharacterValidator = characterValidator([
|
||||
@@ -41,7 +42,7 @@ export const SanitizedCloudflareConnectionSchema = z.discriminatedUnion("method"
|
||||
BaseCloudflareConnectionSchema.extend({
|
||||
method: z.literal(CloudflareConnectionMethod.APIToken),
|
||||
credentials: CloudflareConnectionApiTokenCredentialsSchema.pick({ accountId: true })
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Cloudflare]} (API Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateCloudflareConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -67,8 +68,10 @@ export const UpdateCloudflareConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Cloudflare));
|
||||
|
||||
export const CloudflareConnectionListItemSchema = z.object({
|
||||
name: z.literal("Cloudflare"),
|
||||
app: z.literal(AppConnection.Cloudflare),
|
||||
methods: z.nativeEnum(CloudflareConnectionMethod).array()
|
||||
});
|
||||
export const CloudflareConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Cloudflare"),
|
||||
app: z.literal(AppConnection.Cloudflare),
|
||||
methods: z.nativeEnum(CloudflareConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Cloudflare] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { DatabricksConnectionMethod } from "./databricks-connection-enums";
|
||||
|
||||
export const DatabricksConnectionServicePrincipalInputCredentialsSchema = z.object({
|
||||
@@ -42,7 +43,7 @@ export const SanitizedDatabricksConnectionSchema = z.discriminatedUnion("method"
|
||||
clientId: true,
|
||||
workspaceUrl: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Databricks]} (Service Principal)` }))
|
||||
]);
|
||||
|
||||
export const ValidateDatabricksConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -68,10 +69,12 @@ export const UpdateDatabricksConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Databricks));
|
||||
|
||||
export const DatabricksConnectionListItemSchema = z.object({
|
||||
name: z.literal("Databricks"),
|
||||
app: z.literal(AppConnection.Databricks),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||
methods: z.nativeEnum(DatabricksConnectionMethod).array()
|
||||
});
|
||||
export const DatabricksConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Databricks"),
|
||||
app: z.literal(AppConnection.Databricks),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||
methods: z.nativeEnum(DatabricksConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Databricks] }));
|
||||
|
||||
+9
-6
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { DigitalOceanConnectionMethod } from "./digital-ocean-connection-constants";
|
||||
|
||||
export const DigitalOceanConnectionMethodSchema = z
|
||||
@@ -36,7 +37,7 @@ export const SanitizedDigitalOceanConnectionSchema = z.discriminatedUnion("metho
|
||||
BaseDigitalOceanConnectionSchema.extend({
|
||||
method: DigitalOceanConnectionMethodSchema,
|
||||
credentials: DigitalOceanConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.DigitalOcean]} (Access Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateDigitalOceanConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -60,8 +61,10 @@ export const UpdateDigitalOceanConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.DigitalOcean));
|
||||
|
||||
export const DigitalOceanConnectionListItemSchema = z.object({
|
||||
name: z.literal("Digital Ocean"),
|
||||
app: z.literal(AppConnection.DigitalOcean),
|
||||
methods: z.nativeEnum(DigitalOceanConnectionMethod).array()
|
||||
});
|
||||
export const DigitalOceanConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Digital Ocean"),
|
||||
app: z.literal(AppConnection.DigitalOcean),
|
||||
methods: z.nativeEnum(DigitalOceanConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.DigitalOcean] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { FlyioConnectionMethod } from "./flyio-connection-enums";
|
||||
|
||||
export const FlyioConnectionAccessTokenCredentialsSchema = z.object({
|
||||
@@ -31,7 +32,7 @@ export const SanitizedFlyioConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseFlyioConnectionSchema.extend({
|
||||
method: z.literal(FlyioConnectionMethod.AccessToken),
|
||||
credentials: FlyioConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Flyio]} (Access Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateFlyioConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -55,8 +56,10 @@ export const UpdateFlyioConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Flyio));
|
||||
|
||||
export const FlyioConnectionListItemSchema = z.object({
|
||||
name: z.literal("Fly.io"),
|
||||
app: z.literal(AppConnection.Flyio),
|
||||
methods: z.nativeEnum(FlyioConnectionMethod).array()
|
||||
});
|
||||
export const FlyioConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Fly.io"),
|
||||
app: z.literal(AppConnection.Flyio),
|
||||
methods: z.nativeEnum(FlyioConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Flyio] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { GcpConnectionMethod } from "./gcp-connection-enums";
|
||||
|
||||
export const GcpConnectionServiceAccountImpersonationCredentialsSchema = z.object({
|
||||
@@ -30,7 +31,9 @@ export const SanitizedGcpConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseGcpConnectionSchema.extend({
|
||||
method: z.literal(GcpConnectionMethod.ServiceAccountImpersonation),
|
||||
credentials: GcpConnectionServiceAccountImpersonationCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(
|
||||
JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GCP]} (Service Account Impersonation)` })
|
||||
)
|
||||
]);
|
||||
|
||||
export const ValidateGcpConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -56,10 +59,12 @@ export const UpdateGcpConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.GCP));
|
||||
|
||||
export const GcpConnectionListItemSchema = z.object({
|
||||
name: z.literal("GCP"),
|
||||
app: z.literal(AppConnection.GCP),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]),
|
||||
methods: z.nativeEnum(GcpConnectionMethod).array()
|
||||
});
|
||||
export const GcpConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("GCP"),
|
||||
app: z.literal(AppConnection.GCP),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]),
|
||||
methods: z.nativeEnum(GcpConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.GCP] }));
|
||||
|
||||
+12
-9
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { GitHubRadarConnectionMethod } from "./github-radar-connection-enums";
|
||||
|
||||
export const GitHubRadarConnectionInputCredentialsSchema = z.object({
|
||||
@@ -53,14 +54,16 @@ export const SanitizedGitHubRadarConnectionSchema = z.discriminatedUnion("method
|
||||
BaseGitHubRadarConnectionSchema.extend({
|
||||
method: z.literal(GitHubRadarConnectionMethod.App),
|
||||
credentials: GitHubRadarConnectionOutputCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitHubRadar]} (GitHub App)` }))
|
||||
]);
|
||||
|
||||
export const GitHubRadarConnectionListItemSchema = z.object({
|
||||
name: z.literal("GitHub Radar"),
|
||||
app: z.literal(AppConnection.GitHubRadar),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]),
|
||||
methods: z.nativeEnum(GitHubRadarConnectionMethod).array(),
|
||||
appClientSlug: z.string().optional()
|
||||
});
|
||||
export const GitHubRadarConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("GitHub Radar"),
|
||||
app: z.literal(AppConnection.GitHubRadar),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]),
|
||||
methods: z.nativeEnum(GitHubRadarConnectionMethod).array(),
|
||||
appClientSlug: z.string().optional()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.GitHubRadar] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { GitHubConnectionMethod } from "./github-connection-enums";
|
||||
|
||||
export const GitHubConnectionOAuthInputCredentialsSchema = z.union([
|
||||
@@ -161,29 +162,31 @@ export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
|
||||
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
|
||||
host: z.string().optional()
|
||||
})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitHub]} (GitHub App)` })),
|
||||
BaseGitHubConnectionSchema.extend({
|
||||
method: z.literal(GitHubConnectionMethod.OAuth),
|
||||
credentials: z.object({
|
||||
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
|
||||
host: z.string().optional()
|
||||
})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitHub]} (OAuth)` })),
|
||||
BaseGitHubConnectionSchema.extend({
|
||||
method: z.literal(GitHubConnectionMethod.Pat),
|
||||
credentials: z.object({
|
||||
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
|
||||
host: z.string().optional()
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitHub]} (Personal Access Token)` }))
|
||||
]);
|
||||
|
||||
export const GitHubConnectionListItemSchema = z.object({
|
||||
name: z.literal("GitHub"),
|
||||
app: z.literal(AppConnection.GitHub),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]),
|
||||
methods: z.nativeEnum(GitHubConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional(),
|
||||
appClientSlug: z.string().optional()
|
||||
});
|
||||
export const GitHubConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("GitHub"),
|
||||
app: z.literal(AppConnection.GitHub),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]),
|
||||
methods: z.nativeEnum(GitHubConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional(),
|
||||
appClientSlug: z.string().optional()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.GitHub] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { GitLabAccessTokenType, GitLabConnectionMethod } from "./gitlab-connection-enums";
|
||||
|
||||
export const GitLabConnectionAccessTokenCredentialsSchema = z.object({
|
||||
@@ -84,13 +85,13 @@ export const SanitizedGitLabConnectionSchema = z.discriminatedUnion("method", [
|
||||
instanceUrl: true,
|
||||
accessTokenType: true
|
||||
})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitLab]} (Access Token)` })),
|
||||
BaseGitLabConnectionSchema.extend({
|
||||
method: z.literal(GitLabConnectionMethod.OAuth),
|
||||
credentials: GitLabConnectionOAuthOutputCredentialsSchema.pick({
|
||||
instanceUrl: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitLab]} (OAuth)` }))
|
||||
]);
|
||||
|
||||
export const ValidateGitLabConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -130,9 +131,11 @@ export const UpdateGitLabConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.GitLab));
|
||||
|
||||
export const GitLabConnectionListItemSchema = z.object({
|
||||
name: z.literal("GitLab"),
|
||||
app: z.literal(AppConnection.GitLab),
|
||||
methods: z.nativeEnum(GitLabConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
});
|
||||
export const GitLabConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("GitLab"),
|
||||
app: z.literal(AppConnection.GitLab),
|
||||
methods: z.nativeEnum(GitLabConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.GitLab] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { HCVaultConnectionMethod } from "./hc-vault-connection-enums";
|
||||
|
||||
const InstanceUrlSchema = z
|
||||
@@ -59,7 +60,7 @@ export const SanitizedHCVaultConnectionSchema = z.discriminatedUnion("method", [
|
||||
namespace: true,
|
||||
instanceUrl: true
|
||||
})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.HCVault]} (Access Token)` })),
|
||||
BaseHCVaultConnectionSchema.extend({
|
||||
method: z.literal(HCVaultConnectionMethod.AppRole),
|
||||
credentials: HCVaultConnectionAppRoleCredentialsSchema.pick({
|
||||
@@ -67,7 +68,7 @@ export const SanitizedHCVaultConnectionSchema = z.discriminatedUnion("method", [
|
||||
instanceUrl: true,
|
||||
roleId: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.HCVault]} (App Role)` }))
|
||||
]);
|
||||
|
||||
export const ValidateHCVaultConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -100,8 +101,10 @@ export const UpdateHCVaultConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.HCVault, { supportsGateways: true }));
|
||||
|
||||
export const HCVaultConnectionListItemSchema = z.object({
|
||||
name: z.literal("HCVault"),
|
||||
app: z.literal(AppConnection.HCVault),
|
||||
methods: z.nativeEnum(HCVaultConnectionMethod).array()
|
||||
});
|
||||
export const HCVaultConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("HCVault"),
|
||||
app: z.literal(AppConnection.HCVault),
|
||||
methods: z.nativeEnum(HCVaultConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.HCVault] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { HerokuConnectionMethod } from "./heroku-connection-enums";
|
||||
|
||||
export const HerokuConnectionAuthTokenCredentialsSchema = z.object({
|
||||
@@ -51,11 +52,11 @@ export const SanitizedHerokuConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseHerokuConnectionSchema.extend({
|
||||
method: z.literal(HerokuConnectionMethod.AuthToken),
|
||||
credentials: HerokuConnectionAuthTokenCredentialsSchema.pick({})
|
||||
}),
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Heroku]} (Auth Token)` })),
|
||||
BaseHerokuConnectionSchema.extend({
|
||||
method: z.literal(HerokuConnectionMethod.OAuth),
|
||||
credentials: HerokuConnectionOAuthOutputCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Heroku]} (OAuth)` }))
|
||||
]);
|
||||
|
||||
export const ValidateHerokuConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -95,9 +96,11 @@ export const UpdateHerokuConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Heroku));
|
||||
|
||||
export const HerokuConnectionListItemSchema = z.object({
|
||||
name: z.literal("Heroku"),
|
||||
app: z.literal(AppConnection.Heroku),
|
||||
methods: z.nativeEnum(HerokuConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
});
|
||||
export const HerokuConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Heroku"),
|
||||
app: z.literal(AppConnection.Heroku),
|
||||
methods: z.nativeEnum(HerokuConnectionMethod).array(),
|
||||
oauthClientId: z.string().optional()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Heroku] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { HumanitecConnectionMethod } from "./humanitec-connection-enums";
|
||||
|
||||
export const HumanitecConnectionAccessTokenCredentialsSchema = z.object({
|
||||
@@ -25,7 +26,7 @@ export const SanitizedHumanitecConnectionSchema = z.discriminatedUnion("method",
|
||||
BaseHumanitecConnectionSchema.extend({
|
||||
method: z.literal(HumanitecConnectionMethod.ApiToken),
|
||||
credentials: HumanitecConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Humanitec]} (API Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateHumanitecConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -51,8 +52,10 @@ export const UpdateHumanitecConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Humanitec));
|
||||
|
||||
export const HumanitecConnectionListItemSchema = z.object({
|
||||
name: z.literal("Humanitec"),
|
||||
app: z.literal(AppConnection.Humanitec),
|
||||
methods: z.nativeEnum(HumanitecConnectionMethod).array()
|
||||
});
|
||||
export const HumanitecConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Humanitec"),
|
||||
app: z.literal(AppConnection.Humanitec),
|
||||
methods: z.nativeEnum(HumanitecConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Humanitec] }));
|
||||
|
||||
+9
-6
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { LaravelForgeConnectionMethod } from "./laravel-forge-connection-enums";
|
||||
|
||||
export const LaravelForgeConnectionApiTokenCredentialsSchema = z.object({
|
||||
@@ -25,7 +26,7 @@ export const SanitizedLaravelForgeConnectionSchema = z.discriminatedUnion("metho
|
||||
BaseLaravelForgeConnectionSchema.extend({
|
||||
method: z.literal(LaravelForgeConnectionMethod.ApiToken),
|
||||
credentials: LaravelForgeConnectionApiTokenCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.LaravelForge]} (API Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateLaravelForgeConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -51,8 +52,10 @@ export const UpdateLaravelForgeConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.LaravelForge));
|
||||
|
||||
export const LaravelForgeConnectionListItemSchema = z.object({
|
||||
name: z.literal("Laravel Forge"),
|
||||
app: z.literal(AppConnection.LaravelForge),
|
||||
methods: z.nativeEnum(LaravelForgeConnectionMethod).array()
|
||||
});
|
||||
export const LaravelForgeConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Laravel Forge"),
|
||||
app: z.literal(AppConnection.LaravelForge),
|
||||
methods: z.nativeEnum(LaravelForgeConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.LaravelForge] }));
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { LdapConnectionMethod, LdapProvider } from "./ldap-connection-enums";
|
||||
|
||||
export const LdapConnectionSimpleBindCredentialsSchema = z.object({
|
||||
@@ -61,7 +62,7 @@ export const SanitizedLdapConnectionSchema = z.discriminatedUnion("method", [
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.LDAP]} (Simple Bind)` }))
|
||||
]);
|
||||
|
||||
export const ValidateLdapConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -91,10 +92,12 @@ export const UpdateLdapConnectionSchema = z
|
||||
})
|
||||
);
|
||||
|
||||
export const LdapConnectionListItemSchema = z.object({
|
||||
name: z.literal("LDAP"),
|
||||
app: z.literal(AppConnection.LDAP),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||
methods: z.nativeEnum(LdapConnectionMethod).array()
|
||||
});
|
||||
export const LdapConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("LDAP"),
|
||||
app: z.literal(AppConnection.LDAP),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||
methods: z.nativeEnum(LdapConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.LDAP] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { BaseSqlUsernameAndPasswordConnectionSchema } from "../shared/sql";
|
||||
import { MsSqlConnectionMethod } from "./mssql-connection-enums";
|
||||
|
||||
@@ -34,7 +35,7 @@ export const SanitizedMsSqlConnectionSchema = z.discriminatedUnion("method", [
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.MsSql]} (Username and Password)` }))
|
||||
]);
|
||||
|
||||
export const ValidateMsSqlConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -68,9 +69,11 @@ export const UpdateMsSqlConnectionSchema = z
|
||||
})
|
||||
);
|
||||
|
||||
export const MsSqlConnectionListItemSchema = z.object({
|
||||
name: z.literal("Microsoft SQL Server"),
|
||||
app: z.literal(AppConnection.MsSql),
|
||||
methods: z.nativeEnum(MsSqlConnectionMethod).array(),
|
||||
supportsPlatformManagement: z.literal(true)
|
||||
});
|
||||
export const MsSqlConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Microsoft SQL Server"),
|
||||
app: z.literal(AppConnection.MsSql),
|
||||
methods: z.nativeEnum(MsSqlConnectionMethod).array(),
|
||||
supportsPlatformManagement: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.MsSql] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { BaseSqlUsernameAndPasswordConnectionSchema } from "../shared/sql";
|
||||
import { MySqlConnectionMethod } from "./mysql-connection-enums";
|
||||
|
||||
@@ -32,7 +33,7 @@ export const SanitizedMySqlConnectionSchema = z.discriminatedUnion("method", [
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.MySql]} (Username and Password)` }))
|
||||
]);
|
||||
|
||||
export const ValidateMySqlConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -66,9 +67,11 @@ export const UpdateMySqlConnectionSchema = z
|
||||
})
|
||||
);
|
||||
|
||||
export const MySqlConnectionListItemSchema = z.object({
|
||||
name: z.literal("MySQL"),
|
||||
app: z.literal(AppConnection.MySql),
|
||||
methods: z.nativeEnum(MySqlConnectionMethod).array(),
|
||||
supportsPlatformManagement: z.literal(true)
|
||||
});
|
||||
export const MySqlConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("MySQL"),
|
||||
app: z.literal(AppConnection.MySql),
|
||||
methods: z.nativeEnum(MySqlConnectionMethod).array(),
|
||||
supportsPlatformManagement: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.MySql] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { NetlifyConnectionMethod } from "./netlify-connection-constants";
|
||||
|
||||
export const NetlifyConnectionMethodSchema = z
|
||||
@@ -36,7 +37,7 @@ export const SanitizedNetlifyConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseNetlifyConnectionSchema.extend({
|
||||
method: NetlifyConnectionMethodSchema,
|
||||
credentials: NetlifyConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Netlify]} (Access Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateNetlifyConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -60,8 +61,10 @@ export const UpdateNetlifyConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Netlify));
|
||||
|
||||
export const NetlifyConnectionListItemSchema = z.object({
|
||||
name: z.literal("Netlify"),
|
||||
app: z.literal(AppConnection.Netlify),
|
||||
methods: z.nativeEnum(NetlifyConnectionMethod).array()
|
||||
});
|
||||
export const NetlifyConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Netlify"),
|
||||
app: z.literal(AppConnection.Netlify),
|
||||
methods: z.nativeEnum(NetlifyConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Netlify] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { NorthflankConnectionMethod } from "./northflank-connection-enums";
|
||||
|
||||
export const NorthflankConnectionApiTokenCredentialsSchema = z.object({
|
||||
@@ -27,7 +28,7 @@ export const SanitizedNorthflankConnectionSchema = z.discriminatedUnion("method"
|
||||
BaseNorthflankConnectionSchema.extend({
|
||||
method: z.literal(NorthflankConnectionMethod.ApiToken),
|
||||
credentials: NorthflankConnectionApiTokenCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Northflank]} (API Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateNorthflankConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -53,8 +54,10 @@ export const UpdateNorthflankConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Northflank));
|
||||
|
||||
export const NorthflankConnectionListItemSchema = z.object({
|
||||
name: z.literal("Northflank"),
|
||||
app: z.literal(AppConnection.Northflank),
|
||||
methods: z.nativeEnum(NorthflankConnectionMethod).array()
|
||||
});
|
||||
export const NorthflankConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Northflank"),
|
||||
app: z.literal(AppConnection.Northflank),
|
||||
methods: z.nativeEnum(NorthflankConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Northflank] }));
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { OktaConnectionMethod } from "./okta-connection-enums";
|
||||
|
||||
export const OktaConnectionApiTokenCredentialsSchema = z.object({
|
||||
@@ -40,7 +41,7 @@ export const SanitizedOktaConnectionSchema = z.discriminatedUnion("method", [
|
||||
credentials: OktaConnectionApiTokenCredentialsSchema.pick({
|
||||
instanceUrl: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Okta]} (API Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateOktaConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -62,8 +63,10 @@ export const UpdateOktaConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Okta));
|
||||
|
||||
export const OktaConnectionListItemSchema = z.object({
|
||||
name: z.literal("Okta"),
|
||||
app: z.literal(AppConnection.Okta),
|
||||
methods: z.nativeEnum(OktaConnectionMethod).array()
|
||||
});
|
||||
export const OktaConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Okta"),
|
||||
app: z.literal(AppConnection.Okta),
|
||||
methods: z.nativeEnum(OktaConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Okta] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { BaseSqlUsernameAndPasswordConnectionSchema } from "../shared/sql";
|
||||
import { PostgresConnectionMethod } from "./postgres-connection-enums";
|
||||
|
||||
@@ -32,7 +33,7 @@ export const SanitizedPostgresConnectionSchema = z.discriminatedUnion("method",
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Postgres]} (Username and Password)` }))
|
||||
]);
|
||||
|
||||
export const ValidatePostgresConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -66,9 +67,11 @@ export const UpdatePostgresConnectionSchema = z
|
||||
})
|
||||
);
|
||||
|
||||
export const PostgresConnectionListItemSchema = z.object({
|
||||
name: z.literal("PostgreSQL"),
|
||||
app: z.literal(AppConnection.Postgres),
|
||||
methods: z.nativeEnum(PostgresConnectionMethod).array(),
|
||||
supportsPlatformManagement: z.literal(true)
|
||||
});
|
||||
export const PostgresConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("PostgreSQL"),
|
||||
app: z.literal(AppConnection.Postgres),
|
||||
methods: z.nativeEnum(PostgresConnectionMethod).array(),
|
||||
supportsPlatformManagement: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Postgres] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { RailwayConnectionMethod } from "./railway-connection-constants";
|
||||
|
||||
export const RailwayConnectionMethodSchema = z
|
||||
@@ -36,7 +37,7 @@ export const SanitizedRailwayConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseRailwayConnectionSchema.extend({
|
||||
method: RailwayConnectionMethodSchema,
|
||||
credentials: RailwayConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Railway]} (Access Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateRailwayConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -60,11 +61,13 @@ export const UpdateRailwayConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Railway));
|
||||
|
||||
export const RailwayConnectionListItemSchema = z.object({
|
||||
name: z.literal("Railway"),
|
||||
app: z.literal(AppConnection.Railway),
|
||||
methods: z.nativeEnum(RailwayConnectionMethod).array()
|
||||
});
|
||||
export const RailwayConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Railway"),
|
||||
app: z.literal(AppConnection.Railway),
|
||||
methods: z.nativeEnum(RailwayConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Railway] }));
|
||||
|
||||
export const RailwayResourceSchema = z.object({
|
||||
node: z.object({
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { RedisConnectionMethod } from "./redis-connection-enums";
|
||||
|
||||
export const BaseRedisUsernameAndPasswordConnectionSchema = z.object({
|
||||
@@ -45,7 +46,7 @@ export const SanitizedRedisConnectionSchema = z.discriminatedUnion("method", [
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Redis]} (Username and Password)` }))
|
||||
]);
|
||||
|
||||
export const ValidateRedisConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -79,9 +80,11 @@ export const UpdateRedisConnectionSchema = z
|
||||
})
|
||||
);
|
||||
|
||||
export const RedisConnectionListItemSchema = z.object({
|
||||
name: z.literal("Redis"),
|
||||
app: z.literal(AppConnection.Redis),
|
||||
methods: z.nativeEnum(RedisConnectionMethod).array(),
|
||||
supportsPlatformManagement: z.literal(false)
|
||||
});
|
||||
export const RedisConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Redis"),
|
||||
app: z.literal(AppConnection.Redis),
|
||||
methods: z.nativeEnum(RedisConnectionMethod).array(),
|
||||
supportsPlatformManagement: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Redis] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { RenderConnectionMethod } from "./render-connection-enums";
|
||||
|
||||
export const RenderConnectionApiKeyCredentialsSchema = z.object({
|
||||
@@ -25,7 +26,7 @@ export const SanitizedRenderConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseRenderConnectionSchema.extend({
|
||||
method: z.literal(RenderConnectionMethod.ApiKey),
|
||||
credentials: RenderConnectionApiKeyCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Render]} (API Key)` }))
|
||||
]);
|
||||
|
||||
export const ValidateRenderConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -49,8 +50,10 @@ export const UpdateRenderConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Render));
|
||||
|
||||
export const RenderConnectionListItemSchema = z.object({
|
||||
name: z.literal("Render"),
|
||||
app: z.literal(AppConnection.Render),
|
||||
methods: z.nativeEnum(RenderConnectionMethod).array()
|
||||
});
|
||||
export const RenderConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Render"),
|
||||
app: z.literal(AppConnection.Render),
|
||||
methods: z.nativeEnum(RenderConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Render] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { SupabaseConnectionMethod } from "./supabase-connection-constants";
|
||||
|
||||
export const SupabaseConnectionMethodSchema = z
|
||||
@@ -39,7 +40,7 @@ export const SanitizedSupabaseConnectionSchema = z.discriminatedUnion("method",
|
||||
credentials: SupabaseConnectionAccessTokenCredentialsSchema.pick({
|
||||
instanceUrl: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Supabase]} (Access Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateSupabaseConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -63,8 +64,10 @@ export const UpdateSupabaseConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Supabase));
|
||||
|
||||
export const SupabaseConnectionListItemSchema = z.object({
|
||||
name: z.literal("Supabase"),
|
||||
app: z.literal(AppConnection.Supabase),
|
||||
methods: z.nativeEnum(SupabaseConnectionMethod).array()
|
||||
});
|
||||
export const SupabaseConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Supabase"),
|
||||
app: z.literal(AppConnection.Supabase),
|
||||
methods: z.nativeEnum(SupabaseConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Supabase] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { TeamCityConnectionMethod } from "./teamcity-connection-enums";
|
||||
|
||||
export const TeamCityConnectionAccessTokenCredentialsSchema = z.object({
|
||||
@@ -37,7 +38,7 @@ export const SanitizedTeamCityConnectionSchema = z.discriminatedUnion("method",
|
||||
credentials: TeamCityConnectionAccessTokenCredentialsSchema.pick({
|
||||
instanceUrl: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.TeamCity]} (Access Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateTeamCityConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -63,8 +64,10 @@ export const UpdateTeamCityConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.TeamCity));
|
||||
|
||||
export const TeamCityConnectionListItemSchema = z.object({
|
||||
name: z.literal("TeamCity"),
|
||||
app: z.literal(AppConnection.TeamCity),
|
||||
methods: z.nativeEnum(TeamCityConnectionMethod).array()
|
||||
});
|
||||
export const TeamCityConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("TeamCity"),
|
||||
app: z.literal(AppConnection.TeamCity),
|
||||
methods: z.nativeEnum(TeamCityConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.TeamCity] }));
|
||||
|
||||
+9
-6
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { TerraformCloudConnectionMethod } from "./terraform-cloud-connection-enums";
|
||||
|
||||
export const TerraformCloudConnectionAccessTokenCredentialsSchema = z.object({
|
||||
@@ -27,7 +28,7 @@ export const SanitizedTerraformCloudConnectionSchema = z.discriminatedUnion("met
|
||||
BaseTerraformCloudConnectionSchema.extend({
|
||||
method: z.literal(TerraformCloudConnectionMethod.ApiToken),
|
||||
credentials: TerraformCloudConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.TerraformCloud]} (API Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateTerraformCloudConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -53,8 +54,10 @@ export const UpdateTerraformCloudConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.TerraformCloud));
|
||||
|
||||
export const TerraformCloudConnectionListItemSchema = z.object({
|
||||
name: z.literal("Terraform Cloud"),
|
||||
app: z.literal(AppConnection.TerraformCloud),
|
||||
methods: z.nativeEnum(TerraformCloudConnectionMethod).array()
|
||||
});
|
||||
export const TerraformCloudConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Terraform Cloud"),
|
||||
app: z.literal(AppConnection.TerraformCloud),
|
||||
methods: z.nativeEnum(TerraformCloudConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.TerraformCloud] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { VercelConnectionMethod } from "./vercel-connection-enums";
|
||||
|
||||
export const VercelConnectionAccessTokenCredentialsSchema = z.object({
|
||||
@@ -27,7 +28,7 @@ export const SanitizedVercelConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseVercelConnectionSchema.extend({
|
||||
method: z.literal(VercelConnectionMethod.ApiToken),
|
||||
credentials: VercelConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Vercel]} (API Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateVercelConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -51,8 +52,10 @@ export const UpdateVercelConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Vercel));
|
||||
|
||||
export const VercelConnectionListItemSchema = z.object({
|
||||
name: z.literal("Vercel"),
|
||||
app: z.literal(AppConnection.Vercel),
|
||||
methods: z.nativeEnum(VercelConnectionMethod).array()
|
||||
});
|
||||
export const VercelConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Vercel"),
|
||||
app: z.literal(AppConnection.Vercel),
|
||||
methods: z.nativeEnum(VercelConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Vercel] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { WindmillConnectionMethod } from "./windmill-connection-enums";
|
||||
|
||||
export const WindmillConnectionAccessTokenCredentialsSchema = z.object({
|
||||
@@ -37,7 +38,7 @@ export const SanitizedWindmillConnectionSchema = z.discriminatedUnion("method",
|
||||
credentials: WindmillConnectionAccessTokenCredentialsSchema.pick({
|
||||
instanceUrl: true
|
||||
})
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Windmill]} (Access Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateWindmillConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -63,8 +64,10 @@ export const UpdateWindmillConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Windmill));
|
||||
|
||||
export const WindmillConnectionListItemSchema = z.object({
|
||||
name: z.literal("Windmill"),
|
||||
app: z.literal(AppConnection.Windmill),
|
||||
methods: z.nativeEnum(WindmillConnectionMethod).array()
|
||||
});
|
||||
export const WindmillConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Windmill"),
|
||||
app: z.literal(AppConnection.Windmill),
|
||||
methods: z.nativeEnum(WindmillConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Windmill] }));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
|
||||
import { ZabbixConnectionMethod } from "./zabbix-connection-enums";
|
||||
|
||||
export const ZabbixConnectionApiTokenCredentialsSchema = z.object({
|
||||
@@ -31,7 +32,7 @@ export const SanitizedZabbixConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseZabbixConnectionSchema.extend({
|
||||
method: z.literal(ZabbixConnectionMethod.ApiToken),
|
||||
credentials: ZabbixConnectionApiTokenCredentialsSchema.pick({ instanceUrl: true })
|
||||
})
|
||||
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Zabbix]} (API Token)` }))
|
||||
]);
|
||||
|
||||
export const ValidateZabbixConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
@@ -55,8 +56,10 @@ export const UpdateZabbixConnectionSchema = z
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Zabbix));
|
||||
|
||||
export const ZabbixConnectionListItemSchema = z.object({
|
||||
name: z.literal("Zabbix"),
|
||||
app: z.literal(AppConnection.Zabbix),
|
||||
methods: z.nativeEnum(ZabbixConnectionMethod).array()
|
||||
});
|
||||
export const ZabbixConnectionListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Zabbix"),
|
||||
app: z.literal(AppConnection.Zabbix),
|
||||
methods: z.nativeEnum(ZabbixConnectionMethod).array()
|
||||
})
|
||||
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Zabbix] }));
|
||||
|
||||
@@ -454,6 +454,30 @@ export const authLoginServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (organizationId) {
|
||||
await auditLogService.createAuditLog({
|
||||
orgId: organizationId,
|
||||
ipAddress: ip,
|
||||
userAgent,
|
||||
userAgentType: getUserAgentType(userAgent),
|
||||
actor: {
|
||||
type: ActorType.USER,
|
||||
metadata: {
|
||||
email: userEnc.email,
|
||||
userId: userEnc.userId,
|
||||
username: userEnc.username,
|
||||
authMethod
|
||||
}
|
||||
},
|
||||
event: {
|
||||
type: EventType.USER_LOGIN,
|
||||
metadata: {
|
||||
organizationId
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
tokens: {
|
||||
accessToken: token.access,
|
||||
@@ -646,6 +670,29 @@ export const authLoginServiceFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
await auditLogService.createAuditLog({
|
||||
orgId: organizationId,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
userAgentType: getUserAgentType(userAgent),
|
||||
actor: {
|
||||
type: ActorType.USER,
|
||||
metadata: {
|
||||
email: user.email,
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
authMethod: decodedToken.authMethod
|
||||
}
|
||||
},
|
||||
event: {
|
||||
type: EventType.SELECT_ORGANIZATION,
|
||||
metadata: {
|
||||
organizationId,
|
||||
organizationName: selectedOrg.name
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...tokens,
|
||||
user,
|
||||
@@ -1039,6 +1086,33 @@ export const authLoginServiceFactory = ({
|
||||
organizationId
|
||||
});
|
||||
|
||||
if (organizationId) {
|
||||
await auditLogService.createAuditLog({
|
||||
orgId: organizationId,
|
||||
ipAddress: ip,
|
||||
userAgent,
|
||||
userAgentType: getUserAgentType(userAgent),
|
||||
actor: {
|
||||
type: ActorType.USER,
|
||||
metadata: {
|
||||
email: userEnc.email,
|
||||
userId: userEnc.userId,
|
||||
username: userEnc.username,
|
||||
authMethod: decodedProviderToken.authMethod
|
||||
}
|
||||
},
|
||||
event: {
|
||||
type: EventType.USER_LOGIN,
|
||||
metadata: {
|
||||
organizationId,
|
||||
...(isAuthMethodSaml(decodedProviderToken.authMethod) && {
|
||||
authProvider: decodedProviderToken.authMethod
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { token, isMfaEnabled: false, user: userEnc, decodedProviderToken } as const;
|
||||
};
|
||||
|
||||
|
||||
+276
-195
@@ -1,7 +1,9 @@
|
||||
import * as x509 from "@peculiar/x509";
|
||||
import acme from "acme-client";
|
||||
import acme, { CsrBuffer } from "acme-client";
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError, CryptographyError, NotFoundError } from "@app/lib/errors";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
@@ -64,6 +66,20 @@ type TAcmeCertificateAuthorityFnsDeps = {
|
||||
projectDAL: Pick<TProjectDALFactory, "findById" | "findOne" | "updateById" | "transaction">;
|
||||
};
|
||||
|
||||
type TOrderCertificateDeps = {
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById">;
|
||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa">;
|
||||
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "update">;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction">;
|
||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
||||
kmsService: Pick<
|
||||
TKmsServiceFactory,
|
||||
"encryptWithKmsKey" | "generateKmsKey" | "createCipherPairWithDataKey" | "decryptWithKmsKey"
|
||||
>;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById" | "findOne" | "updateById" | "transaction">;
|
||||
};
|
||||
|
||||
type DBConfigurationColumn = {
|
||||
dnsProvider: string;
|
||||
directoryUrl: string;
|
||||
@@ -104,6 +120,245 @@ export const castDbEntryToAcmeCertificateAuthority = (
|
||||
};
|
||||
};
|
||||
|
||||
export const orderCertificate = async (
|
||||
{
|
||||
caId,
|
||||
subscriberId,
|
||||
commonName,
|
||||
altNames,
|
||||
csr,
|
||||
csrPrivateKey,
|
||||
keyUsages,
|
||||
extendedKeyUsages
|
||||
}: {
|
||||
caId: string;
|
||||
subscriberId?: string;
|
||||
commonName: string;
|
||||
altNames?: string[];
|
||||
csr: CsrBuffer;
|
||||
csrPrivateKey?: string;
|
||||
keyUsages?: CertKeyUsage[];
|
||||
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||
},
|
||||
deps: TOrderCertificateDeps,
|
||||
tx?: Knex
|
||||
) => {
|
||||
const {
|
||||
appConnectionDAL,
|
||||
certificateAuthorityDAL,
|
||||
externalCertificateAuthorityDAL,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
projectDAL
|
||||
} = deps;
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId, tx);
|
||||
if (!ca.externalCa || ca.externalCa.type !== CaType.ACME) {
|
||||
throw new BadRequestError({ message: "CA is not an ACME CA" });
|
||||
}
|
||||
|
||||
const acmeCa = castDbEntryToAcmeCertificateAuthority(ca);
|
||||
if (acmeCa.status !== CaStatus.ACTIVE) {
|
||||
throw new BadRequestError({ message: "CA is disabled" });
|
||||
}
|
||||
|
||||
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||
projectId: ca.projectId,
|
||||
projectDAL,
|
||||
kmsService
|
||||
});
|
||||
|
||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||
kmsId: certificateManagerKmsId
|
||||
});
|
||||
|
||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||
kmsId: certificateManagerKmsId
|
||||
});
|
||||
|
||||
let accountKey: Buffer | undefined;
|
||||
if (acmeCa.credentials) {
|
||||
const decryptedCredentials = await kmsDecryptor({
|
||||
cipherTextBlob: acmeCa.credentials as Buffer
|
||||
});
|
||||
|
||||
const parsedCredentials = await AcmeCertificateAuthorityCredentialsSchema.parseAsync(
|
||||
JSON.parse(decryptedCredentials.toString("utf8"))
|
||||
);
|
||||
|
||||
accountKey = Buffer.from(parsedCredentials.accountKey, "base64");
|
||||
}
|
||||
if (!accountKey) {
|
||||
accountKey = await acme.crypto.createPrivateRsaKey();
|
||||
const newCredentials = {
|
||||
accountKey: accountKey.toString("base64")
|
||||
};
|
||||
const { cipherTextBlob: encryptedNewCredentials } = await kmsEncryptor({
|
||||
plainText: Buffer.from(JSON.stringify(newCredentials))
|
||||
});
|
||||
await externalCertificateAuthorityDAL.update(
|
||||
{
|
||||
caId: acmeCa.id
|
||||
},
|
||||
{
|
||||
credentials: encryptedNewCredentials
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await blockLocalAndPrivateIpAddresses(acmeCa.configuration.directoryUrl);
|
||||
|
||||
const acmeClientOptions: acme.ClientOptions = {
|
||||
directoryUrl: acmeCa.configuration.directoryUrl,
|
||||
accountKey
|
||||
};
|
||||
|
||||
if (acmeCa.configuration.eabKid && acmeCa.configuration.eabHmacKey) {
|
||||
acmeClientOptions.externalAccountBinding = {
|
||||
kid: acmeCa.configuration.eabKid,
|
||||
hmacKey: acmeCa.configuration.eabHmacKey
|
||||
};
|
||||
}
|
||||
|
||||
const acmeClient = new acme.Client(acmeClientOptions);
|
||||
|
||||
const appConnection = await appConnectionDAL.findById(acmeCa.configuration.dnsAppConnectionId);
|
||||
const connection = await decryptAppConnection(appConnection, kmsService);
|
||||
|
||||
const pem = await acmeClient.auto({
|
||||
csr,
|
||||
email: acmeCa.configuration.accountEmail,
|
||||
challengePriority: ["dns-01"],
|
||||
// For ACME development mode, we mock the DNS challenge API calls. So, no real DNS records are created.
|
||||
// We need to disable the challenge verification to avoid errors.
|
||||
skipChallengeVerification: getConfig().isAcmeDevelopmentMode && getConfig().ACME_SKIP_UPSTREAM_VALIDATION,
|
||||
termsOfServiceAgreed: true,
|
||||
|
||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
|
||||
if (challenge.type !== "dns-01") {
|
||||
throw new Error("Unsupported challenge type");
|
||||
}
|
||||
|
||||
const recordName = `_acme-challenge.${authz.identifier.value}`; // e.g., "_acme-challenge.example.com"
|
||||
const recordValue = `"${keyAuthorization}"`; // must be double quoted
|
||||
|
||||
switch (acmeCa.configuration.dnsProviderConfig.provider) {
|
||||
case AcmeDnsProvider.Route53: {
|
||||
await route53InsertTxtRecord(
|
||||
connection as TAwsConnection,
|
||||
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
|
||||
recordName,
|
||||
recordValue
|
||||
);
|
||||
break;
|
||||
}
|
||||
case AcmeDnsProvider.Cloudflare: {
|
||||
await cloudflareInsertTxtRecord(
|
||||
connection as TCloudflareConnection,
|
||||
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
|
||||
recordName,
|
||||
recordValue
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unsupported DNS provider: ${acmeCa.configuration.dnsProviderConfig.provider as string}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {
|
||||
const recordName = `_acme-challenge.${authz.identifier.value}`; // e.g., "_acme-challenge.example.com"
|
||||
const recordValue = `"${keyAuthorization}"`; // must be double quoted
|
||||
|
||||
switch (acmeCa.configuration.dnsProviderConfig.provider) {
|
||||
case AcmeDnsProvider.Route53: {
|
||||
await route53DeleteTxtRecord(
|
||||
connection as TAwsConnection,
|
||||
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
|
||||
recordName,
|
||||
recordValue
|
||||
);
|
||||
break;
|
||||
}
|
||||
case AcmeDnsProvider.Cloudflare: {
|
||||
await cloudflareDeleteTxtRecord(
|
||||
connection as TCloudflareConnection,
|
||||
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
|
||||
recordName,
|
||||
recordValue
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unsupported DNS provider: ${acmeCa.configuration.dnsProviderConfig.provider as string}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [leafCert, parentCert] = acme.crypto.splitPemChain(pem);
|
||||
const certObj = new x509.X509Certificate(leafCert);
|
||||
|
||||
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||
plainText: Buffer.from(new Uint8Array(certObj.rawData))
|
||||
});
|
||||
|
||||
const certificateChainPem = parentCert.trim();
|
||||
|
||||
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||
plainText: Buffer.from(certificateChainPem)
|
||||
});
|
||||
|
||||
const { cipherTextBlob: encryptedPrivateKey } = csrPrivateKey
|
||||
? await kmsEncryptor({
|
||||
plainText: Buffer.from(csrPrivateKey)
|
||||
})
|
||||
: { cipherTextBlob: undefined };
|
||||
|
||||
return (tx || certificateDAL).transaction(async (innerTx: Knex) => {
|
||||
const cert = await certificateDAL.create(
|
||||
{
|
||||
caId: ca.id,
|
||||
pkiSubscriberId: subscriberId,
|
||||
status: CertStatus.ACTIVE,
|
||||
friendlyName: commonName,
|
||||
commonName,
|
||||
altNames: altNames?.join(","),
|
||||
serialNumber: certObj.serialNumber,
|
||||
notBefore: certObj.notBefore,
|
||||
notAfter: certObj.notAfter,
|
||||
keyUsages,
|
||||
extendedKeyUsages,
|
||||
projectId: ca.projectId
|
||||
},
|
||||
innerTx
|
||||
);
|
||||
|
||||
await certificateBodyDAL.create(
|
||||
{
|
||||
certId: cert.id,
|
||||
encryptedCertificate,
|
||||
encryptedCertificateChain
|
||||
},
|
||||
innerTx
|
||||
);
|
||||
|
||||
if (encryptedPrivateKey !== undefined) {
|
||||
await certificateSecretDAL.create(
|
||||
{
|
||||
certId: cert.id,
|
||||
encryptedPrivateKey
|
||||
},
|
||||
innerTx
|
||||
);
|
||||
}
|
||||
|
||||
return cert;
|
||||
});
|
||||
};
|
||||
|
||||
export const AcmeCertificateAuthorityFns = ({
|
||||
appConnectionDAL,
|
||||
appConnectionService,
|
||||
@@ -322,77 +577,6 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
if (!subscriber.caId) {
|
||||
throw new BadRequestError({ message: "Subscriber does not have a CA" });
|
||||
}
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(subscriber.caId);
|
||||
if (!ca.externalCa || ca.externalCa.type !== CaType.ACME) {
|
||||
throw new BadRequestError({ message: "CA is not an ACME CA" });
|
||||
}
|
||||
|
||||
const acmeCa = castDbEntryToAcmeCertificateAuthority(ca);
|
||||
if (acmeCa.status !== CaStatus.ACTIVE) {
|
||||
throw new BadRequestError({ message: "CA is disabled" });
|
||||
}
|
||||
|
||||
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||
projectId: ca.projectId,
|
||||
projectDAL,
|
||||
kmsService
|
||||
});
|
||||
|
||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||
kmsId: certificateManagerKmsId
|
||||
});
|
||||
|
||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||
kmsId: certificateManagerKmsId
|
||||
});
|
||||
|
||||
let accountKey: Buffer | undefined;
|
||||
if (acmeCa.credentials) {
|
||||
const decryptedCredentials = await kmsDecryptor({
|
||||
cipherTextBlob: acmeCa.credentials as Buffer
|
||||
});
|
||||
|
||||
const parsedCredentials = await AcmeCertificateAuthorityCredentialsSchema.parseAsync(
|
||||
JSON.parse(decryptedCredentials.toString("utf8"))
|
||||
);
|
||||
|
||||
accountKey = Buffer.from(parsedCredentials.accountKey, "base64");
|
||||
}
|
||||
if (!accountKey) {
|
||||
accountKey = await acme.crypto.createPrivateRsaKey();
|
||||
const newCredentials = {
|
||||
accountKey: accountKey.toString("base64")
|
||||
};
|
||||
const { cipherTextBlob: encryptedNewCredentials } = await kmsEncryptor({
|
||||
plainText: Buffer.from(JSON.stringify(newCredentials))
|
||||
});
|
||||
await externalCertificateAuthorityDAL.update(
|
||||
{
|
||||
caId: acmeCa.id
|
||||
},
|
||||
{
|
||||
credentials: encryptedNewCredentials
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await blockLocalAndPrivateIpAddresses(acmeCa.configuration.directoryUrl);
|
||||
|
||||
const acmeClientOptions: acme.ClientOptions = {
|
||||
directoryUrl: acmeCa.configuration.directoryUrl,
|
||||
accountKey
|
||||
};
|
||||
|
||||
if (acmeCa.configuration.eabKid && acmeCa.configuration.eabHmacKey) {
|
||||
acmeClientOptions.externalAccountBinding = {
|
||||
kid: acmeCa.configuration.eabKid,
|
||||
hmacKey: acmeCa.configuration.eabHmacKey
|
||||
};
|
||||
}
|
||||
|
||||
const acmeClient = new acme.Client(acmeClientOptions);
|
||||
|
||||
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
||||
|
||||
const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||
@@ -407,131 +591,28 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
skLeaf
|
||||
);
|
||||
|
||||
const appConnection = await appConnectionDAL.findById(acmeCa.configuration.dnsAppConnectionId);
|
||||
const connection = await decryptAppConnection(appConnection, kmsService);
|
||||
|
||||
const pem = await acmeClient.auto({
|
||||
csr: certificateCsr,
|
||||
email: acmeCa.configuration.accountEmail,
|
||||
challengePriority: ["dns-01"],
|
||||
termsOfServiceAgreed: true,
|
||||
|
||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
|
||||
if (challenge.type !== "dns-01") {
|
||||
throw new Error("Unsupported challenge type");
|
||||
}
|
||||
|
||||
const recordName = `_acme-challenge.${authz.identifier.value}`; // e.g., "_acme-challenge.example.com"
|
||||
const recordValue = `"${keyAuthorization}"`; // must be double quoted
|
||||
|
||||
switch (acmeCa.configuration.dnsProviderConfig.provider) {
|
||||
case AcmeDnsProvider.Route53: {
|
||||
await route53InsertTxtRecord(
|
||||
connection as TAwsConnection,
|
||||
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
|
||||
recordName,
|
||||
recordValue
|
||||
);
|
||||
break;
|
||||
}
|
||||
case AcmeDnsProvider.Cloudflare: {
|
||||
await cloudflareInsertTxtRecord(
|
||||
connection as TCloudflareConnection,
|
||||
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
|
||||
recordName,
|
||||
recordValue
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unsupported DNS provider: ${acmeCa.configuration.dnsProviderConfig.provider as string}`);
|
||||
}
|
||||
}
|
||||
await orderCertificate(
|
||||
{
|
||||
caId: subscriber.caId,
|
||||
subscriberId: subscriber.id,
|
||||
commonName: subscriber.commonName,
|
||||
altNames: subscriber.subjectAlternativeNames,
|
||||
csr: certificateCsr,
|
||||
csrPrivateKey: skLeaf,
|
||||
keyUsages: subscriber.keyUsages as CertKeyUsage[],
|
||||
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
|
||||
},
|
||||
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {
|
||||
const recordName = `_acme-challenge.${authz.identifier.value}`; // e.g., "_acme-challenge.example.com"
|
||||
const recordValue = `"${keyAuthorization}"`; // must be double quoted
|
||||
|
||||
switch (acmeCa.configuration.dnsProviderConfig.provider) {
|
||||
case AcmeDnsProvider.Route53: {
|
||||
await route53DeleteTxtRecord(
|
||||
connection as TAwsConnection,
|
||||
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
|
||||
recordName,
|
||||
recordValue
|
||||
);
|
||||
break;
|
||||
}
|
||||
case AcmeDnsProvider.Cloudflare: {
|
||||
await cloudflareDeleteTxtRecord(
|
||||
connection as TCloudflareConnection,
|
||||
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
|
||||
recordName,
|
||||
recordValue
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unsupported DNS provider: ${acmeCa.configuration.dnsProviderConfig.provider as string}`);
|
||||
}
|
||||
}
|
||||
{
|
||||
appConnectionDAL,
|
||||
certificateAuthorityDAL,
|
||||
externalCertificateAuthorityDAL,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
projectDAL
|
||||
}
|
||||
});
|
||||
|
||||
const [leafCert, parentCert] = acme.crypto.splitPemChain(pem);
|
||||
const certObj = new x509.X509Certificate(leafCert);
|
||||
|
||||
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||
plainText: Buffer.from(new Uint8Array(certObj.rawData))
|
||||
});
|
||||
|
||||
const certificateChainPem = parentCert.trim();
|
||||
|
||||
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||
plainText: Buffer.from(certificateChainPem)
|
||||
});
|
||||
|
||||
const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({
|
||||
plainText: Buffer.from(skLeaf)
|
||||
});
|
||||
|
||||
await certificateDAL.transaction(async (tx) => {
|
||||
const cert = await certificateDAL.create(
|
||||
{
|
||||
caId: ca.id,
|
||||
pkiSubscriberId: subscriber.id,
|
||||
status: CertStatus.ACTIVE,
|
||||
friendlyName: subscriber.commonName,
|
||||
commonName: subscriber.commonName,
|
||||
altNames: subscriber.subjectAlternativeNames.join(","),
|
||||
serialNumber: certObj.serialNumber,
|
||||
notBefore: certObj.notBefore,
|
||||
notAfter: certObj.notAfter,
|
||||
keyUsages: subscriber.keyUsages as CertKeyUsage[],
|
||||
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[],
|
||||
projectId: ca.projectId
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await certificateBodyDAL.create(
|
||||
{
|
||||
certId: cert.id,
|
||||
encryptedCertificate,
|
||||
encryptedCertificateChain
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await certificateSecretDAL.create(
|
||||
{
|
||||
certId: cert.id,
|
||||
encryptedPrivateKey
|
||||
},
|
||||
tx
|
||||
);
|
||||
});
|
||||
|
||||
);
|
||||
await triggerAutoSyncForSubscriber(subscriber.id, { pkiSyncDAL, pkiSyncQueue });
|
||||
};
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
const findByIdWithConfigs = async (id: string, tx?: Knex): Promise<TCertificateProfileWithConfigs | undefined> => {
|
||||
try {
|
||||
const query = (tx || db)(TableName.PkiCertificateProfile)
|
||||
.leftJoin(TableName.Project, `${TableName.PkiCertificateProfile}.projectId`, `${TableName.Project}.id`)
|
||||
.leftJoin(
|
||||
TableName.CertificateAuthority,
|
||||
`${TableName.PkiCertificateProfile}.caId`,
|
||||
@@ -112,6 +113,8 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
)
|
||||
.select(selectAllTableCols(TableName.PkiCertificateProfile))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.Project).as("projectId"),
|
||||
db.ref("orgId").withSchema(TableName.Project).as("orgId"),
|
||||
db.ref("id").withSchema(TableName.CertificateAuthority).as("caId"),
|
||||
db.ref("projectId").withSchema(TableName.CertificateAuthority).as("caProjectId"),
|
||||
db.ref("status").withSchema(TableName.CertificateAuthority).as("caStatus"),
|
||||
@@ -165,15 +168,12 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
} as TCertificateProfileWithConfigs["acmeConfig"])
|
||||
: undefined;
|
||||
|
||||
const certificateAuthority =
|
||||
result.caId && result.caProjectId && result.caStatus && result.caName
|
||||
? ({
|
||||
id: result.caId,
|
||||
projectId: result.caProjectId,
|
||||
status: result.caStatus,
|
||||
name: result.caName
|
||||
} as TCertificateProfileWithConfigs["certificateAuthority"])
|
||||
: undefined;
|
||||
const certificateAuthority = {
|
||||
id: result.caId,
|
||||
projectId: result.caProjectId,
|
||||
status: result.caStatus,
|
||||
name: result.caName
|
||||
} as TCertificateProfileWithConfigs["certificateAuthority"];
|
||||
|
||||
const certificateTemplate =
|
||||
result.templateId && result.templateProjectId && result.templateName
|
||||
@@ -185,6 +185,11 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
} as TCertificateProfileWithConfigs["certificateTemplate"])
|
||||
: undefined;
|
||||
|
||||
const project = {
|
||||
id: result.projectId,
|
||||
orgId: result.orgId
|
||||
} as TCertificateProfileWithConfigs["project"];
|
||||
|
||||
const transformedResult: TCertificateProfileWithConfigs = {
|
||||
id: result.id,
|
||||
projectId: result.projectId,
|
||||
@@ -201,6 +206,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
estConfig,
|
||||
apiConfig,
|
||||
acmeConfig,
|
||||
project,
|
||||
certificateAuthority,
|
||||
certificateTemplate
|
||||
};
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import type { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
|
||||
import { ActorType, AuthMethod } from "../auth/auth-type";
|
||||
import type { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal";
|
||||
@@ -166,6 +167,10 @@ describe("CertificateProfileService", () => {
|
||||
})
|
||||
} as unknown as Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
|
||||
const mockLicenseService = {
|
||||
getPlan: vi.fn()
|
||||
} as unknown as Pick<TLicenseServiceFactory, "getPlan">;
|
||||
|
||||
const mockKmsService = {
|
||||
encryptWithKmsKey: vi
|
||||
.fn()
|
||||
@@ -252,6 +257,7 @@ describe("CertificateProfileService", () => {
|
||||
certificateAuthorityDAL: mockCertificateAuthorityDAL,
|
||||
certificateAuthorityCertDAL: mockCertificateAuthorityCertDAL,
|
||||
permissionService: mockPermissionService,
|
||||
licenseService: mockLicenseService,
|
||||
kmsService: mockKmsService,
|
||||
projectDAL: mockProjectDAL
|
||||
});
|
||||
@@ -275,6 +281,13 @@ describe("CertificateProfileService", () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(mockProjectDAL.findById as any).mockResolvedValue({
|
||||
id: "project-123",
|
||||
orgId: "org-123"
|
||||
});
|
||||
(mockLicenseService.getPlan as any).mockResolvedValue({
|
||||
pkiAcme: true
|
||||
});
|
||||
(mockCertificateTemplateV2DAL.findById as any).mockResolvedValue(sampleTemplate);
|
||||
(mockCertificateProfileDAL.findByNameAndProjectId as any).mockResolvedValue(null);
|
||||
(mockCertificateProfileDAL.findBySlugAndProjectId as any).mockResolvedValue(null);
|
||||
@@ -405,6 +418,24 @@ describe("CertificateProfileService", () => {
|
||||
expect(result).toEqual(sampleProfile);
|
||||
expect(mockCertificateTemplateV2DAL.findById).toHaveBeenCalledWith("template-123");
|
||||
});
|
||||
|
||||
it("should throw BadRequestError when plan does not support ACME", async () => {
|
||||
(mockLicenseService.getPlan as any).mockResolvedValue({
|
||||
pkiAcme: false
|
||||
});
|
||||
|
||||
await expect(
|
||||
service.createProfile({
|
||||
...mockActor,
|
||||
projectId: "project-123",
|
||||
data: validProfileData
|
||||
})
|
||||
).rejects.toThrowError(
|
||||
new BadRequestError({
|
||||
message: "Failed to create certificate profile: Plan restriction. Upgrade plan to continue"
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateProfile", () => {
|
||||
@@ -699,6 +730,13 @@ describe("CertificateProfileService", () => {
|
||||
}
|
||||
};
|
||||
|
||||
(mockProjectDAL.findById as any).mockResolvedValue({
|
||||
id: "project-123",
|
||||
orgId: "org-123"
|
||||
});
|
||||
(mockLicenseService.getPlan as any).mockResolvedValue({
|
||||
pkiAcme: true
|
||||
});
|
||||
(mockCertificateTemplateV2DAL.findById as any).mockResolvedValue(sampleTemplate);
|
||||
(mockCertificateProfileDAL.findByNameAndProjectId as any).mockResolvedValue(null);
|
||||
(mockCertificateProfileDAL.findBySlugAndProjectId as any).mockResolvedValue(null);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import * as x509 from "@peculiar/x509";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
ProjectPermissionCertificateActions,
|
||||
@@ -152,6 +153,7 @@ type TCertificateProfileServiceFactoryDep = {
|
||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
|
||||
};
|
||||
@@ -174,6 +176,7 @@ export const certificateProfileServiceFactory = ({
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
permissionService,
|
||||
licenseService,
|
||||
kmsService,
|
||||
projectDAL
|
||||
}: TCertificateProfileServiceFactoryDep) => {
|
||||
@@ -205,6 +208,17 @@ export const certificateProfileServiceFactory = ({
|
||||
ProjectPermissionSub.CertificateProfiles
|
||||
);
|
||||
|
||||
const project = await projectDAL.findById(projectId);
|
||||
if (!project) {
|
||||
throw new NotFoundError({ message: "Project not found" });
|
||||
}
|
||||
const plan = await licenseService.getPlan(project.orgId);
|
||||
if (!plan.pkiAcme) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to create certificate profile: Plan restriction. Upgrade plan to continue"
|
||||
});
|
||||
}
|
||||
|
||||
// Validate that certificate template exists and belongs to the same project
|
||||
if (data.certificateTemplateId) {
|
||||
const template = await certificateTemplateV2DAL.findById(data.certificateTemplateId);
|
||||
|
||||
@@ -33,6 +33,10 @@ export type TCertificateProfileUpdate = Omit<TPkiCertificateProfilesUpdate, "enr
|
||||
};
|
||||
|
||||
export type TCertificateProfileWithConfigs = TCertificateProfile & {
|
||||
project?: {
|
||||
id: string;
|
||||
orgId: string;
|
||||
};
|
||||
certificateAuthority?: {
|
||||
id: string;
|
||||
projectId: string;
|
||||
|
||||
@@ -227,27 +227,25 @@ export const identityAccessTokenServiceFactory = ({
|
||||
});
|
||||
|
||||
if (!identityOrgMembership) {
|
||||
throw new BadRequestError({ message: "Identity does not belong to any organization" });
|
||||
throw new BadRequestError({ message: "Identity does not belong to this organization" });
|
||||
}
|
||||
orgId = subOrganization.id;
|
||||
orgName = subOrganization.name;
|
||||
|
||||
parentOrgId = subOrganization.parentOrgId as string;
|
||||
} else {
|
||||
const organization = await orgDAL.findOne({ id: rootOrgId });
|
||||
|
||||
const identityOrgMembership = await membershipIdentityDAL.findOne({
|
||||
scope: AccessScope.Organization,
|
||||
actorIdentityId: identityAccessToken.identityId,
|
||||
scopeOrgId: rootOrgId
|
||||
scopeOrgId: identityOrgDetails.id
|
||||
});
|
||||
|
||||
if (!identityOrgMembership) {
|
||||
throw new BadRequestError({ message: "Identity does not belong to any organization" });
|
||||
throw new BadRequestError({ message: "Identity does not belong to this organization" });
|
||||
}
|
||||
|
||||
orgId = rootOrgId;
|
||||
orgName = organization.name;
|
||||
orgId = identityOrgDetails.id;
|
||||
orgName = identityOrgDetails.name;
|
||||
parentOrgId = rootOrgId;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { AccessScope, ActionProjectType, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
@@ -51,7 +52,7 @@ type TIdentityAliCloudAuthServiceFactoryDep = {
|
||||
>;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
};
|
||||
|
||||
@@ -105,7 +106,18 @@ export const identityAliCloudAuthServiceFactory = ({
|
||||
// Generate the token
|
||||
const identityAccessToken = await identityAliCloudAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.ALICLOUD_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
@@ -200,7 +212,7 @@ export const identityAliCloudAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -214,16 +226,34 @@ export const identityAliCloudAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -280,7 +310,7 @@ export const identityAliCloudAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -300,16 +330,31 @@ export const identityAliCloudAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -350,7 +395,7 @@ export const identityAliCloudAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -362,15 +407,31 @@ export const identityAliCloudAuthServiceFactory = ({
|
||||
|
||||
const alicloudIdentityAuth = await identityAliCloudAuthDAL.findOne({ identityId });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
return { ...alicloudIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
|
||||
};
|
||||
|
||||
@@ -389,7 +450,7 @@ export const identityAliCloudAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
|
||||
@@ -397,45 +458,61 @@ export const identityAliCloudAuthServiceFactory = ({
|
||||
message: "The identity does not have Alibaba Cloud auth"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke Alibaba Cloud auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke Alibaba Cloud auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const revokedIdentityAliCloudAuth = await identityAliCloudAuthDAL.transaction(async (tx) => {
|
||||
const deletedAliCloudAuth = await identityAliCloudAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.ALICLOUD_AUTH }, tx);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import axios from "axios";
|
||||
import RE2 from "re2";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { AccessScope, ActionProjectType, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import {
|
||||
@@ -50,7 +52,7 @@ type TIdentityAwsAuthServiceFactoryDep = {
|
||||
identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
};
|
||||
|
||||
@@ -179,7 +181,18 @@ export const identityAwsAuthServiceFactory = ({
|
||||
|
||||
const identityAccessToken = await identityAwsAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.AWS_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
@@ -286,7 +299,7 @@ export const identityAwsAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -300,16 +313,34 @@ export const identityAwsAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -370,7 +401,7 @@ export const identityAwsAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -389,16 +420,31 @@ export const identityAwsAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -441,7 +487,7 @@ export const identityAwsAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -453,15 +499,31 @@ export const identityAwsAuthServiceFactory = ({
|
||||
|
||||
const awsIdentityAuth = await identityAwsAuthDAL.findOne({ identityId });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
return { ...awsIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
|
||||
};
|
||||
|
||||
@@ -480,7 +542,7 @@ export const identityAwsAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) {
|
||||
@@ -488,45 +550,60 @@ export const identityAwsAuthServiceFactory = ({
|
||||
message: "The identity does not have aws auth"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke aws auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke aws auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
const revokedIdentityAwsAuth = await identityAwsAuthDAL.transaction(async (tx) => {
|
||||
const deletedAwsAuth = await identityAwsAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.AWS_AUTH }, tx);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { AccessScope, ActionProjectType, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import {
|
||||
@@ -46,7 +47,7 @@ type TIdentityAzureAuthServiceFactoryDep = {
|
||||
>;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
|
||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
};
|
||||
@@ -99,7 +100,18 @@ export const identityAzureAuthServiceFactory = ({
|
||||
|
||||
const identityAccessToken = await identityAzureAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.AZURE_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
@@ -192,7 +204,7 @@ export const identityAzureAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -205,16 +217,34 @@ export const identityAzureAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -275,7 +305,7 @@ export const identityAzureAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) {
|
||||
@@ -293,16 +323,31 @@ export const identityAzureAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -348,7 +393,7 @@ export const identityAzureAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) {
|
||||
@@ -359,16 +404,31 @@ export const identityAzureAuthServiceFactory = ({
|
||||
|
||||
const identityAzureAuth = await identityAzureAuthDAL.findOne({ identityId });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
return { ...identityAzureAuth, orgId: identityMembershipOrg.scopeOrgId };
|
||||
};
|
||||
|
||||
@@ -387,7 +447,7 @@ export const identityAzureAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) {
|
||||
@@ -395,43 +455,59 @@ export const identityAzureAuthServiceFactory = ({
|
||||
message: "The identity does not have azure auth"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke azure auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke azure auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const revokedIdentityAzureAuth = await identityAzureAuthDAL.transaction(async (tx) => {
|
||||
const deletedAzureAuth = await identityAzureAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.AZURE_AUTH }, tx);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { AccessScope, ActionProjectType, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import {
|
||||
@@ -44,7 +45,7 @@ type TIdentityGcpAuthServiceFactoryDep = {
|
||||
identityGcpAuthDAL: Pick<TIdentityGcpAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
|
||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
};
|
||||
@@ -139,7 +140,18 @@ export const identityGcpAuthServiceFactory = ({
|
||||
|
||||
const identityAccessToken = await identityGcpAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.GCP_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
@@ -232,7 +244,7 @@ export const identityGcpAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -246,16 +258,34 @@ export const identityGcpAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -317,7 +347,7 @@ export const identityGcpAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -336,16 +366,31 @@ export const identityGcpAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -392,7 +437,7 @@ export const identityGcpAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -404,16 +449,31 @@ export const identityGcpAuthServiceFactory = ({
|
||||
|
||||
const identityGcpAuth = await identityGcpAuthDAL.findOne({ identityId });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
return { ...identityGcpAuth, orgId: identityMembershipOrg.scopeOrgId };
|
||||
};
|
||||
|
||||
@@ -432,7 +492,7 @@ export const identityGcpAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -441,43 +501,58 @@ export const identityGcpAuthServiceFactory = ({
|
||||
message: "The identity does not have gcp auth"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke gcp auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke gcp auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const revokedIdentityGcpAuth = await identityGcpAuthDAL.transaction(async (tx) => {
|
||||
const deletedGcpAuth = await identityGcpAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.GCP_AUTH }, tx);
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
import https from "https";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { JwksClient } from "jwks-rsa";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope, TIdentityJwtAuthsUpdate } from "@app/db/schemas";
|
||||
import {
|
||||
AccessScope,
|
||||
ActionProjectType,
|
||||
IdentityAuthMethod,
|
||||
OrganizationActionScope,
|
||||
TIdentityJwtAuthsUpdate
|
||||
} from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
@@ -12,6 +18,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import {
|
||||
@@ -50,7 +57,7 @@ type TIdentityJwtAuthServiceFactoryDep = {
|
||||
identityJwtAuthDAL: TIdentityJwtAuthDALFactory;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
|
||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
@@ -213,8 +220,22 @@ export const identityJwtAuthServiceFactory = ({
|
||||
|
||||
const identityAccessToken = await identityJwtAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
{ lastLoginAuthMethod: IdentityAuthMethod.JWT_AUTH, lastLoginTime: new Date() },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.JWT_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
},
|
||||
tx
|
||||
);
|
||||
const newToken = await identityAccessTokenDAL.create(
|
||||
@@ -309,7 +330,7 @@ export const identityJwtAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) {
|
||||
@@ -322,16 +343,35 @@ export const identityJwtAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
@@ -416,7 +456,7 @@ export const identityJwtAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -435,17 +475,32 @@ export const identityJwtAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -524,7 +579,7 @@ export const identityJwtAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -534,17 +589,32 @@ export const identityJwtAuthServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
const identityJwtAuth = await identityJwtAuthDAL.findOne({ identityId });
|
||||
|
||||
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
@@ -576,7 +646,7 @@ export const identityJwtAuthServiceFactory = ({
|
||||
if (!identityMembershipOrg) {
|
||||
throw new NotFoundError({ message: "Failed to find identity" });
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -586,45 +656,60 @@ export const identityJwtAuthServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke jwt auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke jwt auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
const revokedIdentityJwtAuth = await identityJwtAuthDAL.transaction(async (tx) => {
|
||||
const deletedJwtAuth = await identityJwtAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.JWT_AUTH }, tx);
|
||||
|
||||
+149
-70
@@ -1,4 +1,4 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
import axios, { AxiosError } from "axios";
|
||||
import https from "https";
|
||||
@@ -6,6 +6,7 @@ import RE2 from "re2";
|
||||
|
||||
import {
|
||||
AccessScope,
|
||||
ActionProjectType,
|
||||
IdentityAuthMethod,
|
||||
OrganizationActionScope,
|
||||
TIdentityKubernetesAuthsUpdate
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import {
|
||||
@@ -69,7 +71,7 @@ type TIdentityKubernetesAuthServiceFactoryDep = {
|
||||
>;
|
||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
gatewayService: TGatewayServiceFactory;
|
||||
@@ -448,8 +450,22 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
|
||||
const identityAccessToken = await identityKubernetesAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
{ lastLoginAuthMethod: IdentityAuthMethod.KUBERNETES_AUTH, lastLoginTime: new Date() },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.KUBERNETES_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
},
|
||||
tx
|
||||
);
|
||||
const newToken = await identityAccessTokenDAL.create(
|
||||
@@ -549,7 +565,7 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -563,16 +579,34 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -679,7 +713,7 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -699,16 +733,31 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
@@ -831,7 +880,7 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -846,16 +895,31 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId: identityMembershipOrg.scopeOrgId
|
||||
@@ -897,7 +961,7 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -906,43 +970,58 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
message: "The identity does not have kubernetes auth"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke kubernetes auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke kubernetes auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
const revokedIdentityKubernetesAuth = await identityKubernetesAuthDAL.transaction(async (tx) => {
|
||||
const deletedKubernetesAuth = await identityKubernetesAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.KUBERNETES_AUTH }, tx);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { AccessScope, ActionProjectType, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { TIdentityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template";
|
||||
import { testLDAPConfig } from "@app/ee/services/ldap-config/ldap-fns";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
@@ -61,7 +62,7 @@ type TIdentityLdapAuthServiceFactoryDep = {
|
||||
>;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
kmsService: TKmsServiceFactory;
|
||||
identityDAL: Pick<TIdentityDALFactory, "findById" | "findOne">;
|
||||
identityAuthTemplateDAL: TIdentityAuthTemplateDALFactory;
|
||||
@@ -177,8 +178,22 @@ export const identityLdapAuthServiceFactory = ({
|
||||
try {
|
||||
const identityAccessToken = await identityLdapAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
{ lastLoginAuthMethod: IdentityAuthMethod.LDAP_AUTH, lastLoginTime: new Date() },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.LDAP_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
},
|
||||
tx
|
||||
);
|
||||
const newToken = await identityAccessTokenDAL.create(
|
||||
@@ -276,7 +291,7 @@ export const identityLdapAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -290,7 +305,7 @@ export const identityLdapAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
@@ -298,10 +313,30 @@ export const identityLdapAuthServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission: projectPermission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(projectPermission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
|
||||
if (templateId) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
|
||||
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||
);
|
||||
@@ -451,7 +486,7 @@ export const identityLdapAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -470,7 +505,7 @@ export const identityLdapAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
@@ -478,10 +513,30 @@ export const identityLdapAuthServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission: projectPermission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(projectPermission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Edit,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
|
||||
if (templateId) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
|
||||
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||
);
|
||||
@@ -618,7 +673,7 @@ export const identityLdapAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -630,14 +685,31 @@ export const identityLdapAuthServiceFactory = ({
|
||||
|
||||
const ldapIdentityAuth = await identityLdapAuthDAL.findOne({ identityId });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
@@ -650,7 +722,6 @@ export const identityLdapAuthServiceFactory = ({
|
||||
? decryptor({ cipherTextBlob: ldapIdentityAuth.encryptedLdapCaCertificate }).toString()
|
||||
: undefined;
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
return { ...ldapIdentityAuth, orgId: identityMembershipOrg.scopeOrgId, bindDN, bindPass, ldapCaCertificate };
|
||||
};
|
||||
|
||||
@@ -669,7 +740,7 @@ export const identityLdapAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
|
||||
@@ -677,45 +748,62 @@ export const identityLdapAuthServiceFactory = ({
|
||||
message: "The identity does not have LDAP Auth attached"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke LDAP auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke LDAP auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const revokedIdentityLdapAuth = await identityLdapAuthDAL.transaction(async (tx) => {
|
||||
const [deletedLdapAuth] = await identityLdapAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.LDAP_AUTH }, tx);
|
||||
@@ -824,15 +912,31 @@ export const identityLdapAuthServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const deleted = await keyStore.deleteItems({
|
||||
pattern: `lockout:identity:${identityId}:${IdentityAuthMethod.LDAP_AUTH}:*`
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
import { AxiosError } from "axios";
|
||||
import RE2 from "re2";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { AccessScope, ActionProjectType, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
@@ -49,7 +50,7 @@ type TIdentityOciAuthServiceFactoryDep = {
|
||||
identityOciAuthDAL: Pick<TIdentityOciAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
};
|
||||
|
||||
@@ -110,8 +111,22 @@ export const identityOciAuthServiceFactory = ({
|
||||
// Generate the token
|
||||
const identityAccessToken = await identityOciAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
{ lastLoginAuthMethod: IdentityAuthMethod.OCI_AUTH, lastLoginTime: new Date() },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.OCI_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
},
|
||||
tx
|
||||
);
|
||||
const newToken = await identityAccessTokenDAL.create(
|
||||
@@ -203,7 +218,7 @@ export const identityOciAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -217,15 +232,34 @@ export const identityOciAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
@@ -285,7 +319,7 @@ export const identityOciAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -304,15 +338,31 @@ export const identityOciAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
@@ -355,7 +405,7 @@ export const identityOciAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -367,15 +417,31 @@ export const identityOciAuthServiceFactory = ({
|
||||
|
||||
const ociIdentityAuth = await identityOciAuthDAL.findOne({ identityId });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
return { ...ociIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
|
||||
};
|
||||
|
||||
@@ -394,7 +460,7 @@ export const identityOciAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) {
|
||||
@@ -402,45 +468,62 @@ export const identityOciAuthServiceFactory = ({
|
||||
message: "The identity does not have OCI auth"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke OCI auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke OCI auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const revokedIdentityOciAuth = await identityOciAuthDAL.transaction(async (tx) => {
|
||||
const deletedOciAuth = await identityOciAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.OCI_AUTH }, tx);
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
import axios from "axios";
|
||||
import https from "https";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { JwksClient } from "jwks-rsa";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope, TIdentityOidcAuthsUpdate } from "@app/db/schemas";
|
||||
import {
|
||||
AccessScope,
|
||||
ActionProjectType,
|
||||
IdentityAuthMethod,
|
||||
OrganizationActionScope,
|
||||
TIdentityOidcAuthsUpdate
|
||||
} from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
@@ -13,6 +19,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import {
|
||||
@@ -23,6 +30,7 @@ import {
|
||||
UnauthorizedError
|
||||
} from "@app/lib/errors";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { AuthAttemptAuthMethod, AuthAttemptAuthResult, authAttemptCounter } from "@app/lib/telemetry/metrics";
|
||||
import { getValueByDot } from "@app/lib/template/dot-access";
|
||||
|
||||
@@ -50,7 +58,7 @@ type TIdentityOidcAuthServiceFactoryDep = {
|
||||
identityOidcAuthDAL: TIdentityOidcAuthDALFactory;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
|
||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
@@ -111,38 +119,106 @@ export const identityOidcAuthServiceFactory = ({
|
||||
requestAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined
|
||||
});
|
||||
|
||||
const { kid } = decodedToken.header as { kid: string };
|
||||
const { kid } = decodedToken.header as { kid?: string };
|
||||
|
||||
let oidcSigningKey;
|
||||
try {
|
||||
oidcSigningKey = await client.getSigningKey(kid);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === "SigningKeyNotFoundError") {
|
||||
let tokenData: Record<string, string> | undefined;
|
||||
|
||||
// If kid is provided, try to get the specific signing key
|
||||
if (kid) {
|
||||
let oidcSigningKey;
|
||||
try {
|
||||
oidcSigningKey = await client.getSigningKey(kid);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === "SigningKeyNotFoundError") {
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: Unable to verify JWT signature. The signing key '${kid}' was not found in the OIDC provider's JWKS endpoint. This may indicate an invalid token or misconfigured OIDC provider.`
|
||||
});
|
||||
}
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: Unable to verify JWT signature. The signing key '${kid}' was not found in the OIDC provider's JWKS endpoint. This may indicate an invalid token or misconfigured OIDC provider.`
|
||||
message: `Access denied: Failed to retrieve signing key from OIDC provider: ${error instanceof Error ? error.message : String(error)}`
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
tokenData = crypto.jwt().verify(oidcJwt, oidcSigningKey.getPublicKey(), {
|
||||
issuer: identityOidcAuth.boundIssuer
|
||||
}) as Record<string, string>;
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.JsonWebTokenError) {
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: ${error.message}`
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
// If kid is not provided, try all available signing keys
|
||||
logger.warn(
|
||||
`OIDC login without KID header [identityId=${identityOidcAuth.identityId}] [orgId=${org.id}] [ip=${requestContext.get("ip")}]`
|
||||
);
|
||||
|
||||
let allSigningKeys;
|
||||
try {
|
||||
allSigningKeys = await client.getSigningKeys();
|
||||
} catch (error) {
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: Failed to retrieve signing keys from OIDC provider: ${error instanceof Error ? error.message : String(error)}`
|
||||
});
|
||||
}
|
||||
|
||||
if (!allSigningKeys || allSigningKeys.length === 0) {
|
||||
throw new UnauthorizedError({
|
||||
message: "Access denied: No signing keys available from OIDC provider's JWKS endpoint."
|
||||
});
|
||||
}
|
||||
|
||||
// Limit the number of keys to try to prevent abuse
|
||||
const MAX_KEYS_TO_TRY = 10;
|
||||
if (allSigningKeys.length > MAX_KEYS_TO_TRY) {
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: OIDC provider has ${allSigningKeys.length} signing keys. Tokens must include 'kid' header when provider has more than ${MAX_KEYS_TO_TRY} keys.`
|
||||
});
|
||||
}
|
||||
|
||||
let lastError: Error | null = null;
|
||||
let verified = false;
|
||||
|
||||
// Try each signing key until one works
|
||||
for (const signingKey of allSigningKeys) {
|
||||
try {
|
||||
tokenData = crypto.jwt().verify(oidcJwt, signingKey.getPublicKey(), {
|
||||
issuer: identityOidcAuth.boundIssuer
|
||||
}) as Record<string, string>;
|
||||
verified = true;
|
||||
break;
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.JsonWebTokenError) {
|
||||
lastError = error;
|
||||
// Continue trying other keys
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!verified) {
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: Unable to verify JWT signature with any available signing key. ${lastError ? lastError.message : "Invalid token"}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure tokenData was successfully assigned
|
||||
if (!tokenData) {
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: Failed to retrieve signing key from OIDC provider: ${error instanceof Error ? error.message : String(error)}`
|
||||
message: "Access denied: Failed to verify JWT token"
|
||||
});
|
||||
}
|
||||
|
||||
let tokenData: Record<string, string>;
|
||||
try {
|
||||
tokenData = crypto.jwt().verify(oidcJwt, oidcSigningKey.getPublicKey(), {
|
||||
issuer: identityOidcAuth.boundIssuer
|
||||
}) as Record<string, string>;
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.JsonWebTokenError) {
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: ${error.message}`
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
const verifiedTokenData: Record<string, string> = tokenData;
|
||||
|
||||
if (identityOidcAuth.boundSubject) {
|
||||
if (!doesFieldValueMatchOidcPolicy(tokenData.sub, identityOidcAuth.boundSubject)) {
|
||||
if (!doesFieldValueMatchOidcPolicy(verifiedTokenData.sub, identityOidcAuth.boundSubject)) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Access denied: OIDC subject not allowed."
|
||||
});
|
||||
@@ -153,7 +229,7 @@ export const identityOidcAuthServiceFactory = ({
|
||||
if (
|
||||
!identityOidcAuth.boundAudiences
|
||||
.split(", ")
|
||||
.some((policyValue) => doesAudValueMatchOidcPolicy(tokenData.aud, policyValue))
|
||||
.some((policyValue) => doesAudValueMatchOidcPolicy(verifiedTokenData.aud, policyValue))
|
||||
) {
|
||||
throw new UnauthorizedError({
|
||||
message: "Access denied: OIDC audience not allowed."
|
||||
@@ -164,7 +240,7 @@ export const identityOidcAuthServiceFactory = ({
|
||||
if (identityOidcAuth.boundClaims) {
|
||||
Object.keys(identityOidcAuth.boundClaims).forEach((claimKey) => {
|
||||
const claimValue = (identityOidcAuth.boundClaims as Record<string, string>)[claimKey];
|
||||
const value = getValueByDot(tokenData, claimKey);
|
||||
const value = getValueByDot(verifiedTokenData, claimKey);
|
||||
|
||||
if (!value) {
|
||||
throw new UnauthorizedError({
|
||||
@@ -185,7 +261,7 @@ export const identityOidcAuthServiceFactory = ({
|
||||
if (identityOidcAuth.claimMetadataMapping) {
|
||||
Object.keys(identityOidcAuth.claimMetadataMapping).forEach((permissionKey) => {
|
||||
const claimKey = (identityOidcAuth.claimMetadataMapping as Record<string, string>)[permissionKey];
|
||||
const value = getValueByDot(tokenData, claimKey);
|
||||
const value = getValueByDot(verifiedTokenData, claimKey);
|
||||
if (!value) {
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: token has no ${claimKey} field`
|
||||
@@ -197,8 +273,22 @@ export const identityOidcAuthServiceFactory = ({
|
||||
|
||||
const identityAccessToken = await identityOidcAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
{ lastLoginAuthMethod: IdentityAuthMethod.OIDC_AUTH, lastLoginTime: new Date() },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.OIDC_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
},
|
||||
tx
|
||||
);
|
||||
const newToken = await identityAccessTokenDAL.create(
|
||||
@@ -249,7 +339,7 @@ export const identityOidcAuthServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
return { accessToken, identityOidcAuth, identityAccessToken, identity, oidcTokenData: tokenData };
|
||||
return { accessToken, identityOidcAuth, identityAccessToken, identity, oidcTokenData: verifiedTokenData };
|
||||
} catch (error) {
|
||||
if (appCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) {
|
||||
authAttemptCounter.add(1, {
|
||||
@@ -297,7 +387,7 @@ export const identityOidcAuthServiceFactory = ({
|
||||
if (!identityMembershipOrg) {
|
||||
throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) {
|
||||
@@ -310,16 +400,35 @@ export const identityOidcAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
@@ -393,7 +502,7 @@ export const identityOidcAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -412,16 +521,32 @@ export const identityOidcAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
@@ -486,7 +611,7 @@ export const identityOidcAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -496,15 +621,31 @@ export const identityOidcAuthServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId });
|
||||
|
||||
@@ -531,7 +672,7 @@ export const identityOidcAuthServiceFactory = ({
|
||||
if (!identityMembershipOrg) {
|
||||
throw new NotFoundError({ message: "Failed to find identity" });
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -541,46 +682,62 @@ export const identityOidcAuthServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke oidc auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke oidc auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const revokedIdentityOidcAuth = await identityOidcAuthDAL.transaction(async (tx) => {
|
||||
const deletedOidcAuth = await identityOidcAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.OIDC_AUTH }, tx);
|
||||
|
||||
@@ -293,7 +293,11 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity),
|
||||
db.ref("id").as("identityId").withSchema(TableName.Identity),
|
||||
db.ref("name").as("identityName").withSchema(TableName.Identity),
|
||||
db.ref("orgId").as("identityOrgId").withSchema(TableName.Identity),
|
||||
db.ref("projectId").as("identityProjectId").withSchema(TableName.Identity),
|
||||
db.ref("id").withSchema(TableName.Membership),
|
||||
db.ref("lastLoginAuthMethod").withSchema(TableName.Membership),
|
||||
db.ref("lastLoginTime").withSchema(TableName.Membership),
|
||||
db.ref("role").withSchema(TableName.MembershipRole),
|
||||
db.ref("id").withSchema(TableName.MembershipRole).as("membershipRoleId"),
|
||||
db.ref("customRoleId").withSchema(TableName.MembershipRole),
|
||||
@@ -334,6 +338,8 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
parentMapper: ({
|
||||
identityId,
|
||||
identityName,
|
||||
identityOrgId,
|
||||
identityProjectId,
|
||||
uaId,
|
||||
alicloudId,
|
||||
awsId,
|
||||
@@ -346,7 +352,9 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
id,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
projectName
|
||||
projectName,
|
||||
lastLoginAuthMethod,
|
||||
lastLoginTime
|
||||
}) => ({
|
||||
id,
|
||||
identityId,
|
||||
@@ -355,6 +363,8 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
identity: {
|
||||
id: identityId,
|
||||
name: identityName,
|
||||
projectId: identityProjectId,
|
||||
orgId: identityOrgId,
|
||||
authMethods: buildAuthMethods({
|
||||
uaId,
|
||||
alicloudId,
|
||||
@@ -367,6 +377,11 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
tokenId
|
||||
})
|
||||
},
|
||||
// TODO: scott - not sure why these aren't properly typed?
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
lastLoginAuthMethod,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
lastLoginTime,
|
||||
project: {
|
||||
id: projectId,
|
||||
name: projectName
|
||||
|
||||
@@ -60,8 +60,14 @@ export const identityProjectServiceFactory = ({
|
||||
});
|
||||
|
||||
const totalCount = await identityProjectDAL.getCountByProjectId(projectId, { search });
|
||||
const filteredMemberships = identityMemberships.filter((el) =>
|
||||
permission.can(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId: el.identity.id })
|
||||
)
|
||||
);
|
||||
|
||||
return { identityMemberships, totalCount };
|
||||
return { identityMemberships: filteredMemberships, totalCount };
|
||||
};
|
||||
|
||||
const getProjectIdentityByIdentityId = async ({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { AccessScope, ActionProjectType, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
@@ -43,7 +44,7 @@ type TIdentityTlsCertAuthServiceFactoryDep = {
|
||||
>;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
};
|
||||
@@ -130,8 +131,22 @@ export const identityTlsCertAuthServiceFactory = ({
|
||||
// Generate the token
|
||||
const identityAccessToken = await identityTlsCertAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
{ lastLoginAuthMethod: IdentityAuthMethod.TLS_CERT_AUTH, lastLoginTime: new Date() },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.TLS_CERT_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
},
|
||||
tx
|
||||
);
|
||||
const newToken = await identityAccessTokenDAL.create(
|
||||
@@ -223,7 +238,7 @@ export const identityTlsCertAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -237,15 +252,34 @@ export const identityTlsCertAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
@@ -309,7 +343,7 @@ export const identityTlsCertAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -329,15 +363,31 @@ export const identityTlsCertAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
@@ -392,7 +442,7 @@ export const identityTlsCertAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -404,15 +454,32 @@ export const identityTlsCertAuthServiceFactory = ({
|
||||
|
||||
const identityAuth = await identityTlsCertAuthDAL.findOne({ identityId });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId: identityMembershipOrg.scopeOrgId
|
||||
@@ -440,7 +507,7 @@ export const identityTlsCertAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) {
|
||||
@@ -448,44 +515,61 @@ export const identityTlsCertAuthServiceFactory = ({
|
||||
message: "The identity does not have TLS Certificate auth"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission, memberships } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const shouldUseNewPrivilegeSystem = Boolean(memberships?.[0]?.shouldUseNewPrivilegeSystem);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke TLS Certificate auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission, memberships } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const shouldUseNewPrivilegeSystem = Boolean(memberships?.[0]?.shouldUseNewPrivilegeSystem);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke TLS Certificate auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const revokedIdentityTlsCertAuth = await identityTlsCertAuthDAL.transaction(async (tx) => {
|
||||
const deletedTlsCertAuth = await identityTlsCertAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.TLS_CERT_AUTH }, tx);
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope, TableName } from "@app/db/schemas";
|
||||
import {
|
||||
AccessScope,
|
||||
ActionProjectType,
|
||||
IdentityAuthMethod,
|
||||
OrganizationActionScope,
|
||||
TableName
|
||||
} from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
@@ -8,6 +14,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import {
|
||||
@@ -49,7 +56,7 @@ type TIdentityTokenAuthServiceFactoryDep = {
|
||||
TIdentityAccessTokenDALFactory,
|
||||
"create" | "find" | "update" | "findById" | "findOne" | "updateById" | "delete"
|
||||
>;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
};
|
||||
@@ -87,7 +94,7 @@ export const identityTokenAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -101,15 +108,34 @@ export const identityTokenAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
@@ -167,7 +193,7 @@ export const identityTokenAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -187,15 +213,31 @@ export const identityTokenAuthServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
@@ -239,7 +281,7 @@ export const identityTokenAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -251,15 +293,31 @@ export const identityTokenAuthServiceFactory = ({
|
||||
|
||||
const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
return { ...identityTokenAuth, orgId: identityMembershipOrg.scopeOrgId };
|
||||
};
|
||||
@@ -282,7 +340,7 @@ export const identityTokenAuthServiceFactory = ({
|
||||
identityId
|
||||
});
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -291,44 +349,61 @@ export const identityTokenAuthServiceFactory = ({
|
||||
message: "The identity does not have Token Auth"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke token auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke token auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const revokedIdentityTokenAuth = await identityTokenAuthDAL.transaction(async (tx) => {
|
||||
const deletedTokenAuth = await identityTokenAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({
|
||||
@@ -367,45 +442,61 @@ export const identityTokenAuthServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to create token for identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.CreateToken,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to create token for identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId });
|
||||
|
||||
const identity = await identityDAL.findById(identityTokenAuth.identityId);
|
||||
@@ -413,7 +504,18 @@ export const identityTokenAuthServiceFactory = ({
|
||||
|
||||
const identityAccessToken = await identityTokenAuthDAL.transaction(async (tx) => {
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{ lastLoginAuthMethod: IdentityAuthMethod.TOKEN_AUTH, lastLoginTime: new Date() },
|
||||
tx
|
||||
);
|
||||
@@ -478,15 +580,32 @@ export const identityTokenAuthServiceFactory = ({
|
||||
message: "The identity does not have Token Auth"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const tokens = await identityAccessTokenDAL.find(
|
||||
{
|
||||
@@ -531,43 +650,60 @@ export const identityTokenAuthServiceFactory = ({
|
||||
message: "The identity does not have Token Auth"
|
||||
});
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update token for identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.CreateToken,
|
||||
subject(ProjectPermissionSub.Identity, { identityId: identityMembershipOrg.identity.id })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update token for identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const [token] = await identityAccessTokenDAL.update(
|
||||
{
|
||||
authMethod: IdentityAuthMethod.TOKEN_AUTH,
|
||||
@@ -603,24 +739,43 @@ export const identityTokenAuthServiceFactory = ({
|
||||
|
||||
await validateIdentityUpdateForSuperAdminPrivileges(identityAccessToken.identityId, isActorSuperAdmin);
|
||||
|
||||
const identityOrgMembership = await membershipIdentityDAL.findOne({
|
||||
actorIdentityId: identityAccessToken.identityId,
|
||||
scope: AccessScope.Organization
|
||||
const identityOrgMembership = await membershipIdentityDAL.getIdentityById({
|
||||
scopeData: {
|
||||
scope: AccessScope.Organization,
|
||||
orgId: actorOrgId
|
||||
},
|
||||
identityId: identityAccessToken.identityId
|
||||
});
|
||||
|
||||
if (!identityOrgMembership) {
|
||||
throw new NotFoundError({ message: `Failed to find identity with ID ${identityAccessToken.identityId}` });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityOrgMembership.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityOrgMembership.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityOrgMembership.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId: identityOrgMembership.identity.id })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityOrgMembership.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const [revokedToken] = await identityAccessTokenDAL.update(
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
|
||||
import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { AccessScope, ActionProjectType, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
@@ -51,7 +52,7 @@ type TIdentityUaServiceFactoryDep = {
|
||||
identityUaClientSecretDAL: TIdentityUaClientSecretDALFactory;
|
||||
identityAccessTokenDAL: TIdentityAccessTokenDALFactory;
|
||||
membershipIdentityDAL: TMembershipIdentityDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
keyStore: Pick<
|
||||
@@ -231,7 +232,18 @@ export const identityUaServiceFactory = ({
|
||||
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
|
||||
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx);
|
||||
await membershipIdentityDAL.update(
|
||||
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
|
||||
identity.projectId
|
||||
? {
|
||||
scope: AccessScope.Project,
|
||||
scopeOrgId: identity.orgId,
|
||||
scopeProjectId: identity.projectId,
|
||||
actorIdentityId: identity.id
|
||||
}
|
||||
: {
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: identity.orgId,
|
||||
actorIdentityId: identity.id
|
||||
},
|
||||
{
|
||||
lastLoginAuthMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
|
||||
lastLoginTime: new Date()
|
||||
@@ -343,7 +355,7 @@ export const identityUaServiceFactory = ({
|
||||
message: "Failed to add universal auth to already configured identity"
|
||||
});
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -351,16 +363,35 @@ export const identityUaServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedClientSecretTrustedIps = clientSecretTrustedIps.map((clientSecretTrustedIp) => {
|
||||
@@ -456,7 +487,7 @@ export const identityUaServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -467,15 +498,31 @@ export const identityUaServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
|
||||
const reformattedClientSecretTrustedIps = clientSecretTrustedIps?.map((clientSecretTrustedIp) => {
|
||||
@@ -550,19 +597,35 @@ export const identityUaServiceFactory = ({
|
||||
message: "The identity does not have universal auth"
|
||||
});
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
return { ...uaIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
|
||||
};
|
||||
|
||||
@@ -587,46 +650,62 @@ export const identityUaServiceFactory = ({
|
||||
message: "The identity does not have universal auth"
|
||||
});
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke universal auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.RevokeAuth,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke universal auth of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
const revokedIdentityUniversalAuth = await identityUaDAL.transaction(async (tx) => {
|
||||
const deletedUniversalAuth = await identityUaDAL.delete({ identityId }, tx);
|
||||
return { ...deletedUniversalAuth?.[0], orgId: identityMembershipOrg.scopeOrgId };
|
||||
@@ -658,47 +737,65 @@ export const identityUaServiceFactory = ({
|
||||
message: "The identity does not have universal auth"
|
||||
});
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to create client secret for identity.",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.CreateToken,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to create client secret for identity.",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.CreateToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
const appCfg = getConfig();
|
||||
const clientSecret = crypto.randomBytes(32).toString("hex");
|
||||
const clientSecretHash = await crypto.hashing().createHash(clientSecret, appCfg.SALT_ROUNDS);
|
||||
@@ -744,47 +841,63 @@ export const identityUaServiceFactory = ({
|
||||
message: "The identity does not have universal auth"
|
||||
});
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GetToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to get identity client secret with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GetToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.GetToken,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GetToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to get identity client secret with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GetToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
const identityUniversalAuth = await identityUaDAL.findOne({
|
||||
identityId
|
||||
});
|
||||
@@ -818,7 +931,7 @@ export const identityUaServiceFactory = ({
|
||||
message: "The identity does not have universal auth"
|
||||
});
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -828,43 +941,57 @@ export const identityUaServiceFactory = ({
|
||||
const clientSecret = await identityUaClientSecretDAL.findOne({ id: clientSecretId, identityUAId: identityUa.id });
|
||||
if (!clientSecret) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GetToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to read identity client secret of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GetToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.GetToken,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GetToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to read identity client secret of identity with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GetToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
return { ...clientSecret, identityId, orgId: identityMembershipOrg.scopeOrgId };
|
||||
};
|
||||
|
||||
@@ -890,7 +1017,7 @@ export const identityUaServiceFactory = ({
|
||||
message: "The identity does not have universal auth"
|
||||
});
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
@@ -900,45 +1027,63 @@ export const identityUaServiceFactory = ({
|
||||
const clientSecret = await identityUaClientSecretDAL.findOne({ id: clientSecretId, identityUAId: identityUa.id });
|
||||
if (!clientSecret) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.DeleteToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid) {
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke identity client secret with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.DeleteToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
}
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.DeleteToken,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionIdentityActions.Delete,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityMembershipOrg.identity.id,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.DeleteToken,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid) {
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke identity client secret with more privileged role",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.DeleteToken,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
}
|
||||
const updatedClientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, {
|
||||
isClientSecretRevoked: true
|
||||
});
|
||||
@@ -967,20 +1112,35 @@ export const identityUaServiceFactory = ({
|
||||
message: "The identity does not have universal auth"
|
||||
});
|
||||
}
|
||||
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
|
||||
if (identityMembershipOrg.identity.orgId !== actorOrgId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
if (identityMembershipOrg.identity.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityMembershipOrg.identity.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
scope: OrganizationActionScope.Any,
|
||||
actor,
|
||||
actorId,
|
||||
orgId: identityMembershipOrg.scopeOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
}
|
||||
const deleted = await keyStore.deleteItems({
|
||||
pattern: `lockout:identity:${identityId}:${IdentityAuthMethod.UNIVERSAL_AUTH}:*`
|
||||
});
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { AccessScope, AccessScopeData, IdentitiesSchema, TableName } from "@app/db/schemas";
|
||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||
|
||||
import { buildAuthMethods } from "../identity/identity-fns";
|
||||
|
||||
export type TIdentityV2DALFactory = ReturnType<typeof identityV2DALFactory>;
|
||||
|
||||
export const identityV2DALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.Identity);
|
||||
|
||||
const getIdentityById = async (scopeData: AccessScopeData, identityId: string) => {
|
||||
const doc = await db
|
||||
.replicaNode()(TableName.Identity)
|
||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||
void queryBuilder.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`);
|
||||
})
|
||||
.leftJoin(
|
||||
TableName.IdentityUniversalAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityUniversalAuth}.identityId`
|
||||
)
|
||||
.leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`)
|
||||
.leftJoin(
|
||||
TableName.IdentityAliCloudAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||
)
|
||||
.leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`)
|
||||
.leftJoin(
|
||||
TableName.IdentityKubernetesAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityKubernetesAuth}.identityId`
|
||||
)
|
||||
.leftJoin(TableName.IdentityOciAuth, `${TableName.Identity}.id`, `${TableName.IdentityOciAuth}.identityId`)
|
||||
.leftJoin(TableName.IdentityOidcAuth, `${TableName.Identity}.id`, `${TableName.IdentityOidcAuth}.identityId`)
|
||||
.leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`)
|
||||
.leftJoin(TableName.IdentityTokenAuth, `${TableName.Identity}.id`, `${TableName.IdentityTokenAuth}.identityId`)
|
||||
.leftJoin(
|
||||
TableName.IdentityTlsCertAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityTlsCertAuth}.identityId`
|
||||
)
|
||||
.leftJoin(TableName.IdentityLdapAuth, `${TableName.Identity}.id`, `${TableName.IdentityLdapAuth}.identityId`)
|
||||
.leftJoin(TableName.IdentityJwtAuth, `${TableName.Identity}.id`, `${TableName.IdentityJwtAuth}.identityId`)
|
||||
.where(`${TableName.Identity}.id`, identityId)
|
||||
.where(`${TableName.Identity}.orgId`, scopeData.orgId)
|
||||
.where((qb) => {
|
||||
if (scopeData.scope === AccessScope.Project) {
|
||||
void qb.where(`${TableName.Identity}.projectId`, scopeData.projectId);
|
||||
} else {
|
||||
void qb.whereNull(`${TableName.Identity}.projectId`);
|
||||
}
|
||||
})
|
||||
.select(
|
||||
selectAllTableCols(TableName.Identity),
|
||||
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
|
||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
||||
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
||||
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
|
||||
db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth),
|
||||
db.ref("id").as("tlsCertId").withSchema(TableName.IdentityTlsCertAuth)
|
||||
);
|
||||
|
||||
if (!doc) return doc;
|
||||
|
||||
const formattedDoc = sqlNestRelationships({
|
||||
data: doc,
|
||||
key: "id",
|
||||
parentMapper: (el) => {
|
||||
const {
|
||||
uaId,
|
||||
awsId,
|
||||
gcpId,
|
||||
kubernetesId,
|
||||
oidcId,
|
||||
azureId,
|
||||
alicloudId,
|
||||
tokenId,
|
||||
jwtId,
|
||||
ociId,
|
||||
ldapId,
|
||||
tlsCertId
|
||||
} = el;
|
||||
return {
|
||||
...IdentitiesSchema.parse(el),
|
||||
authMethods: buildAuthMethods({
|
||||
uaId,
|
||||
awsId,
|
||||
gcpId,
|
||||
kubernetesId,
|
||||
oidcId,
|
||||
azureId,
|
||||
tokenId,
|
||||
alicloudId,
|
||||
jwtId,
|
||||
ldapId,
|
||||
ociId,
|
||||
tlsCertId
|
||||
})
|
||||
};
|
||||
},
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "metadataId",
|
||||
label: "metadata" as const,
|
||||
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||
id: metadataId,
|
||||
key: metadataKey,
|
||||
value: metadataValue
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return formattedDoc?.[0];
|
||||
};
|
||||
|
||||
const listIdentities = async (
|
||||
scopeData: AccessScopeData,
|
||||
filter: { limit?: number; offset?: number; search?: string } = {}
|
||||
) => {
|
||||
const query = db
|
||||
.replicaNode()(TableName.Identity)
|
||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||
void queryBuilder.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`);
|
||||
})
|
||||
.where(`${TableName.Identity}.orgId`, scopeData.orgId)
|
||||
.where((qb) => {
|
||||
if (scopeData.scope === AccessScope.Project) {
|
||||
void qb.where(`${TableName.Identity}.projectId`, scopeData.projectId);
|
||||
} else {
|
||||
void qb.whereNull(`${TableName.Identity}.projectId`);
|
||||
}
|
||||
})
|
||||
.select(
|
||||
selectAllTableCols(TableName.Identity),
|
||||
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
|
||||
)
|
||||
.select(db.raw(`count(distinct ??) over () as ??`, [`${TableName.Identity}.id`, "count"]));
|
||||
|
||||
if (filter.limit) void query.limit(filter.limit);
|
||||
if (filter.offset) void query.offset(filter.offset || 0);
|
||||
|
||||
if (filter.search) void query.whereILike(`${TableName.Identity}.name`, `%${filter.search}%`);
|
||||
|
||||
const docs = await query;
|
||||
|
||||
const formattedDoc = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "id",
|
||||
parentMapper: (el) => IdentitiesSchema.parse(el),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "metadataId",
|
||||
label: "metadata" as const,
|
||||
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||
id: metadataId,
|
||||
key: metadataKey,
|
||||
value: metadataValue
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return { docs: formattedDoc, count: Number((docs?.[0] as unknown as { count: number })?.count) };
|
||||
};
|
||||
|
||||
return { ...orm, listIdentities, getIdentityById };
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
|
||||
export const getIdentityActiveLockoutAuthMethods = async (
|
||||
identityId: string,
|
||||
keyStore: Pick<TKeyStoreFactory, "getKeysByPattern" | "getItem">
|
||||
) => {
|
||||
const activeLockouts = await keyStore.getKeysByPattern(`lockout:identity:${identityId}:*`);
|
||||
|
||||
const activeLockoutAuthMethods = new Set<string>();
|
||||
for await (const key of activeLockouts) {
|
||||
const parts = key.split(":");
|
||||
if (parts.length > 3) {
|
||||
const lockoutRaw = await keyStore.getItem(key);
|
||||
if (lockoutRaw) {
|
||||
const lockout = JSON.parse(lockoutRaw) as { lockedOut: boolean };
|
||||
if (lockout.lockedOut) {
|
||||
activeLockoutAuthMethods.add(parts[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(activeLockoutAuthMethods);
|
||||
};
|
||||
@@ -0,0 +1,251 @@
|
||||
import { AccessScope, OrgMembershipRole } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { getIdentityActiveLockoutAuthMethods } from "@app/services/identity-v2/identity-fns";
|
||||
|
||||
import { TIdentityMetadataDALFactory } from "../identity/identity-metadata-dal";
|
||||
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
||||
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
|
||||
import { TIdentityV2DALFactory } from "./identity-dal";
|
||||
import {
|
||||
TCreateIdentityV2DTO,
|
||||
TDeleteIdentityV2DTO,
|
||||
TGetIdentityByIdV2DTO,
|
||||
TListIdentityV2DTO,
|
||||
TUpdateIdentityV2DTO
|
||||
} from "./identity-types";
|
||||
import { newOrgIdentityFactory } from "./org/org-identity-factory";
|
||||
import { newProjectIdentityFactory } from "./project/project-identity-factory";
|
||||
|
||||
type TScopedIdentityV2ServiceFactoryDep = {
|
||||
identityDAL: TIdentityV2DALFactory;
|
||||
permissionService: TPermissionServiceFactory;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||
membershipIdentityDAL: TMembershipIdentityDALFactory;
|
||||
membershipRoleDAL: TMembershipRoleDALFactory;
|
||||
identityMetadataDAL: TIdentityMetadataDALFactory;
|
||||
keyStore: Pick<TKeyStoreFactory, "getKeysByPattern" | "getItem">;
|
||||
};
|
||||
|
||||
export type TScopedIdentityV2ServiceFactory = ReturnType<typeof identityV2ServiceFactory>;
|
||||
|
||||
export const identityV2ServiceFactory = ({
|
||||
identityDAL,
|
||||
permissionService,
|
||||
licenseService,
|
||||
membershipIdentityDAL,
|
||||
membershipRoleDAL,
|
||||
identityMetadataDAL,
|
||||
keyStore
|
||||
}: TScopedIdentityV2ServiceFactoryDep) => {
|
||||
const orgFactory = newOrgIdentityFactory({
|
||||
permissionService
|
||||
});
|
||||
const projectFactory = newProjectIdentityFactory({
|
||||
permissionService
|
||||
});
|
||||
|
||||
const scopeFactory = {
|
||||
[AccessScope.Organization]: orgFactory,
|
||||
[AccessScope.Project]: projectFactory,
|
||||
// namespace will get stripped off
|
||||
[AccessScope.Namespace]: orgFactory
|
||||
};
|
||||
|
||||
const createIdentity = async (dto: TCreateIdentityV2DTO) => {
|
||||
const { scopeData, data } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
await factory.onCreateIdentityGuard(dto);
|
||||
|
||||
const plan = await licenseService.getPlan(dto.permission.orgId);
|
||||
|
||||
if (plan?.slug !== "enterprise" && 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 newIdentity = await identityDAL.create(
|
||||
{
|
||||
name: data.name,
|
||||
hasDeleteProtection: data.hasDeleteProtection,
|
||||
orgId: dto.permission.orgId,
|
||||
projectId: scopeData.scope === AccessScope.Project ? scopeData.projectId : null
|
||||
},
|
||||
tx
|
||||
);
|
||||
const orgMembership = await membershipIdentityDAL.create(
|
||||
{
|
||||
scope: AccessScope.Organization,
|
||||
actorIdentityId: newIdentity.id,
|
||||
scopeOrgId: dto.permission.orgId
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
const newMembershipIds = [orgMembership.id];
|
||||
if (scopeData.scope === AccessScope.Project) {
|
||||
const projectMembership = await membershipIdentityDAL.create(
|
||||
{
|
||||
scope: AccessScope.Project,
|
||||
actorIdentityId: newIdentity.id,
|
||||
scopeOrgId: dto.permission.orgId,
|
||||
scopeProjectId: scopeData.projectId
|
||||
},
|
||||
tx
|
||||
);
|
||||
newMembershipIds.push(projectMembership.id);
|
||||
}
|
||||
|
||||
await membershipRoleDAL.insertMany(
|
||||
newMembershipIds.map((membershipId) => ({
|
||||
membershipId,
|
||||
role: OrgMembershipRole.NoAccess
|
||||
})),
|
||||
tx
|
||||
);
|
||||
|
||||
let insertedMetadata: Array<{
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}> = [];
|
||||
|
||||
if (data.metadata && data.metadata.length) {
|
||||
const rowsToInsert = data.metadata.map(({ key, value }) => ({
|
||||
identityId: newIdentity.id,
|
||||
orgId: newIdentity.orgId,
|
||||
key,
|
||||
value
|
||||
}));
|
||||
|
||||
insertedMetadata = await identityMetadataDAL.insertMany(rowsToInsert, tx);
|
||||
}
|
||||
|
||||
return {
|
||||
...newIdentity,
|
||||
authMethods: [],
|
||||
metadata: insertedMetadata
|
||||
};
|
||||
});
|
||||
await licenseService.updateSubscriptionOrgMemberCount(dto.permission.orgId);
|
||||
|
||||
return { identity };
|
||||
};
|
||||
|
||||
const updateIdentity = async (dto: TUpdateIdentityV2DTO) => {
|
||||
const { scopeData, data } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
await factory.onUpdateIdentityGuard(dto);
|
||||
const existingIdentity = await identityDAL.findOne({
|
||||
id: dto.selector.identityId,
|
||||
orgId: dto.permission.orgId,
|
||||
projectId: dto.scopeData.scope === AccessScope.Project ? dto.scopeData.projectId : null
|
||||
});
|
||||
if (!existingIdentity)
|
||||
throw new NotFoundError({ message: `Identity with id ${dto.selector.identityId} not found` });
|
||||
|
||||
const identity = await identityDAL.transaction(async (tx) => {
|
||||
const updatedIdentity =
|
||||
data?.name || data?.hasDeleteProtection
|
||||
? await identityDAL.updateById(
|
||||
dto.selector.identityId,
|
||||
{ name: data.name, hasDeleteProtection: data.hasDeleteProtection },
|
||||
tx
|
||||
)
|
||||
: existingIdentity;
|
||||
|
||||
let insertedMetadata: Array<{
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}> = [];
|
||||
|
||||
if (data.metadata) {
|
||||
await identityMetadataDAL.delete({ orgId: dto.permission.orgId, identityId: dto.selector.identityId }, tx);
|
||||
|
||||
if (data.metadata.length) {
|
||||
const rowsToInsert = data.metadata.map(({ key, value }) => ({
|
||||
identityId: updatedIdentity.id,
|
||||
orgId: updatedIdentity.orgId,
|
||||
key,
|
||||
value
|
||||
}));
|
||||
|
||||
insertedMetadata = await identityMetadataDAL.insertMany(rowsToInsert, tx);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...updatedIdentity,
|
||||
metadata: insertedMetadata
|
||||
};
|
||||
});
|
||||
|
||||
return { identity };
|
||||
};
|
||||
|
||||
const deleteIdentity = async (dto: TDeleteIdentityV2DTO) => {
|
||||
const { scopeData } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
await factory.onDeleteIdentityGuard(dto);
|
||||
|
||||
const existingIdentity = await identityDAL.findOne({
|
||||
id: dto.selector.identityId,
|
||||
orgId: dto.permission.orgId,
|
||||
projectId: dto.scopeData.scope === AccessScope.Project ? dto.scopeData.projectId : null
|
||||
});
|
||||
if (!existingIdentity)
|
||||
throw new NotFoundError({ message: `Identity with id ${dto.selector.identityId} not found` });
|
||||
|
||||
const deletedIdentity = await identityDAL.deleteById(dto.selector.identityId);
|
||||
|
||||
await licenseService.updateSubscriptionOrgMemberCount(scopeData.orgId);
|
||||
|
||||
return { identity: deletedIdentity };
|
||||
};
|
||||
|
||||
const getIdentityById = async (dto: TGetIdentityByIdV2DTO) => {
|
||||
const { scopeData } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
await factory.onGetIdentityByIdGuard(dto);
|
||||
|
||||
const identity = await identityDAL.getIdentityById(dto.scopeData, dto.selector.identityId);
|
||||
if (!identity) throw new NotFoundError({ message: `Identity with id ${dto.selector.identityId} not found` });
|
||||
|
||||
const activeLockoutAuthMethods = await getIdentityActiveLockoutAuthMethods(identity.id, keyStore);
|
||||
|
||||
return { identity: { ...identity, activeLockoutAuthMethods } };
|
||||
};
|
||||
|
||||
const listIdentities = async (dto: TListIdentityV2DTO) => {
|
||||
const { scopeData } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
const isIdentityAccessible = await factory.onListIdentityGuard(dto);
|
||||
|
||||
const identities = await identityDAL.listIdentities(dto.scopeData, {
|
||||
search: dto.data.search,
|
||||
offset: dto.data.offset,
|
||||
limit: dto.data.limit
|
||||
});
|
||||
|
||||
return { ...identities, docs: identities.docs.filter((el) => isIdentityAccessible({ identityId: el.id })) };
|
||||
};
|
||||
|
||||
return {
|
||||
createIdentity,
|
||||
updateIdentity,
|
||||
deleteIdentity,
|
||||
getIdentityById,
|
||||
listIdentities
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import { AccessScopeData } from "@app/db/schemas";
|
||||
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
export interface TIdentityV2Factory {
|
||||
onCreateIdentityGuard: (arg: TCreateIdentityV2DTO) => Promise<void>;
|
||||
onUpdateIdentityGuard: (arg: TUpdateIdentityV2DTO) => Promise<void>;
|
||||
onDeleteIdentityGuard: (arg: TDeleteIdentityV2DTO) => Promise<void>;
|
||||
onListIdentityGuard: (arg: TListIdentityV2DTO) => Promise<(arg: { identityId: string }) => boolean>;
|
||||
onGetIdentityByIdGuard: (arg: TGetIdentityByIdV2DTO) => Promise<void>;
|
||||
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
|
||||
}
|
||||
|
||||
export enum IdentityOrderBy {
|
||||
Name = "name",
|
||||
Role = "role"
|
||||
}
|
||||
|
||||
export type TCreateIdentityV2DTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
data: {
|
||||
name: string;
|
||||
hasDeleteProtection: boolean;
|
||||
metadata?: { key: string; value: string }[];
|
||||
};
|
||||
};
|
||||
|
||||
export type TUpdateIdentityV2DTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
identityId: string;
|
||||
};
|
||||
data: Partial<{
|
||||
name: string;
|
||||
hasDeleteProtection: boolean;
|
||||
metadata?: { key: string; value: string }[];
|
||||
}>;
|
||||
};
|
||||
|
||||
export type TDeleteIdentityV2DTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
identityId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TGetIdentityByIdV2DTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
identityId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TListIdentityV2DTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
data: Partial<{
|
||||
limit: number;
|
||||
offset: number;
|
||||
orderBy: IdentityOrderBy;
|
||||
orderDirection: OrderByDirection;
|
||||
search: string;
|
||||
}>;
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { AccessScope, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { InternalServerError } from "@app/lib/errors";
|
||||
|
||||
import { TIdentityV2Factory } from "../identity-types";
|
||||
|
||||
type TOrgIdentityFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
};
|
||||
|
||||
export const newOrgIdentityFactory = ({ permissionService }: TOrgIdentityFactoryDep): TIdentityV2Factory => {
|
||||
const getScopeField: TIdentityV2Factory["getScopeField"] = (scopeData) => {
|
||||
if (scopeData.scope === AccessScope.Organization) {
|
||||
return { key: "orgId" as const, value: scopeData.orgId };
|
||||
}
|
||||
throw new InternalServerError({ message: "Invalid scope provided for the org factory" });
|
||||
};
|
||||
|
||||
const onCreateIdentityGuard: TIdentityV2Factory["onCreateIdentityGuard"] = async (dto) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
orgId: dto.permission.orgId,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
actorOrgId: dto.permission.orgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
};
|
||||
|
||||
const onUpdateIdentityGuard: TIdentityV2Factory["onUpdateIdentityGuard"] = async (dto) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
orgId: dto.permission.orgId,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
actorOrgId: dto.permission.orgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
};
|
||||
|
||||
const onDeleteIdentityGuard: TIdentityV2Factory["onDeleteIdentityGuard"] = async (dto) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
orgId: dto.permission.orgId,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
actorOrgId: dto.permission.orgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
|
||||
};
|
||||
|
||||
const onListIdentityGuard: TIdentityV2Factory["onListIdentityGuard"] = async (dto) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
orgId: dto.permission.orgId,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
actorOrgId: dto.permission.orgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
|
||||
return () => true;
|
||||
};
|
||||
|
||||
const onGetIdentityByIdGuard: TIdentityV2Factory["onGetIdentityByIdGuard"] = async (dto) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
orgId: dto.permission.orgId,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
actorOrgId: dto.permission.orgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateIdentityGuard,
|
||||
onUpdateIdentityGuard,
|
||||
onDeleteIdentityGuard,
|
||||
onListIdentityGuard,
|
||||
onGetIdentityByIdGuard,
|
||||
getScopeField
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { AccessScope, ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { InternalServerError } from "@app/lib/errors";
|
||||
|
||||
import { TIdentityV2Factory } from "../identity-types";
|
||||
|
||||
type TProjectIdentityFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
};
|
||||
|
||||
export const newProjectIdentityFactory = ({ permissionService }: TProjectIdentityFactoryDep): TIdentityV2Factory => {
|
||||
const getScopeField: TIdentityV2Factory["getScopeField"] = (scopeData) => {
|
||||
if (scopeData.scope === AccessScope.Project) {
|
||||
return { key: "projectId" as const, value: scopeData.projectId };
|
||||
}
|
||||
throw new InternalServerError({ message: "Invalid scope provided for the project factory" });
|
||||
};
|
||||
|
||||
const onCreateIdentityGuard: TIdentityV2Factory["onCreateIdentityGuard"] = async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
projectId: scope.value,
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
ProjectPermissionSub.Identity
|
||||
);
|
||||
};
|
||||
|
||||
const onUpdateIdentityGuard: TIdentityV2Factory["onUpdateIdentityGuard"] = async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
projectId: scope.value,
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId: dto.selector.identityId })
|
||||
);
|
||||
};
|
||||
|
||||
const onDeleteIdentityGuard: TIdentityV2Factory["onDeleteIdentityGuard"] = async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
projectId: scope.value,
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Delete,
|
||||
subject(ProjectPermissionSub.Identity, { identityId: dto.selector.identityId })
|
||||
);
|
||||
};
|
||||
|
||||
const onListIdentityGuard: TIdentityV2Factory["onListIdentityGuard"] = async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
projectId: scope.value,
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
ProjectPermissionSub.Identity
|
||||
);
|
||||
|
||||
return (arg) =>
|
||||
permission.can(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId: arg.identityId })
|
||||
);
|
||||
};
|
||||
|
||||
const onGetIdentityByIdGuard: TIdentityV2Factory["onGetIdentityByIdGuard"] = async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
projectId: scope.value,
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId: dto.selector.identityId })
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateIdentityGuard,
|
||||
onUpdateIdentityGuard,
|
||||
onDeleteIdentityGuard,
|
||||
onListIdentityGuard,
|
||||
onGetIdentityByIdGuard,
|
||||
getScopeField
|
||||
};
|
||||
};
|
||||
@@ -159,6 +159,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
.join(TableName.Membership, `${TableName.Membership}.actorIdentityId`, `${TableName.Identity}.id`)
|
||||
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
|
||||
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
|
||||
.whereNull(`${TableName.Identity}.projectId`)
|
||||
.orderBy(`${TableName.Identity}.${orderBy}`, orderDirection)
|
||||
.select(
|
||||
selectAllTableCols(TableName.Membership),
|
||||
@@ -188,9 +189,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
)
|
||||
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||
void queryBuilder
|
||||
.on(`paginatedIdentity.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`)
|
||||
.andOn(`paginatedIdentity.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||
void queryBuilder.on(`paginatedIdentity.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`);
|
||||
})
|
||||
.leftJoin<TIdentityUniversalAuths>(
|
||||
TableName.IdentityUniversalAuth,
|
||||
@@ -404,6 +403,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
|
||||
.where(`${TableName.Membership}.scopeOrgId`, orgId)
|
||||
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Membership}.actorIdentityId`)
|
||||
.whereNull(`${TableName.Identity}.projectId`)
|
||||
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
|
||||
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
||||
.orderBy(
|
||||
@@ -447,9 +447,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
|
||||
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||
void queryBuilder
|
||||
.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`)
|
||||
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||
void queryBuilder.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`);
|
||||
})
|
||||
.leftJoin(
|
||||
TableName.IdentityUniversalAuth,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||
import { getIdentityActiveLockoutAuthMethods } from "@app/services/identity-v2/identity-fns";
|
||||
|
||||
import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
|
||||
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
||||
@@ -142,7 +143,7 @@ export const identityServiceFactory = ({
|
||||
if (metadata && metadata.length) {
|
||||
const rowsToInsert = metadata.map(({ key, value }) => ({
|
||||
identityId: newIdentity.id,
|
||||
orgId,
|
||||
orgId: newIdentity.orgId,
|
||||
key,
|
||||
value
|
||||
}));
|
||||
@@ -220,6 +221,11 @@ export const identityServiceFactory = ({
|
||||
}
|
||||
|
||||
const identityDetails = await identityDAL.findById(id);
|
||||
|
||||
if (identityDetails.projectId) {
|
||||
throw new BadRequestError({ message: `Identity is managed by project` });
|
||||
}
|
||||
|
||||
const identity = await identityDAL.transaction(async (tx) => {
|
||||
const newIdentity =
|
||||
identityDetails.orgId === actorOrgId && (name || hasDeleteProtection)
|
||||
@@ -243,13 +249,13 @@ export const identityServiceFactory = ({
|
||||
value: string;
|
||||
}> = [];
|
||||
|
||||
if (metadata) {
|
||||
await identityMetadataDAL.delete({ orgId: identityOrgMembership.scopeOrgId, identityId: id }, tx);
|
||||
if (metadata && identityDetails.orgId === actorOrgId) {
|
||||
await identityMetadataDAL.delete({ orgId: newIdentity.orgId, identityId: id }, tx);
|
||||
|
||||
if (metadata.length) {
|
||||
const rowsToInsert = metadata.map(({ key, value }) => ({
|
||||
identityId: newIdentity.id,
|
||||
orgId: identityOrgMembership.scopeOrgId,
|
||||
orgId: newIdentity.orgId,
|
||||
key,
|
||||
value
|
||||
}));
|
||||
@@ -286,25 +292,11 @@ export const identityServiceFactory = ({
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
|
||||
const activeLockouts = await keyStore.getKeysByPattern(`lockout:identity:${id}:*`);
|
||||
|
||||
const activeLockoutAuthMethods = new Set<string>();
|
||||
for await (const key of activeLockouts) {
|
||||
const parts = key.split(":");
|
||||
if (parts.length > 3) {
|
||||
const lockoutRaw = await keyStore.getItem(key);
|
||||
if (lockoutRaw) {
|
||||
const lockout = JSON.parse(lockoutRaw) as { lockedOut: boolean };
|
||||
if (lockout.lockedOut) {
|
||||
activeLockoutAuthMethods.add(parts[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const activeLockoutAuthMethods = await getIdentityActiveLockoutAuthMethods(id, keyStore);
|
||||
|
||||
return {
|
||||
...identity,
|
||||
identity: { ...identity.identity, activeLockoutAuthMethods: Array.from(activeLockoutAuthMethods) }
|
||||
identity: { ...identity.identity, activeLockoutAuthMethods }
|
||||
};
|
||||
};
|
||||
|
||||
@@ -337,10 +329,14 @@ export const identityServiceFactory = ({
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
|
||||
|
||||
if (identityOrgMembership.identity.hasDeleteProtection)
|
||||
throw new BadRequestError({ message: "Identity has delete protection" });
|
||||
if (identityOrgMembership.identity.projectId) {
|
||||
throw new BadRequestError({ message: `Identity is managed by project` });
|
||||
}
|
||||
|
||||
if (identityOrgMembership.identity.orgId === actorOrgId) {
|
||||
if (identityOrgMembership.identity.hasDeleteProtection)
|
||||
throw new BadRequestError({ message: "Identity has delete protection" });
|
||||
|
||||
if (identityOrgMembership.identity.identityOrgId === actorOrgId) {
|
||||
const deletedIdentity = await identityDAL.deleteById(id);
|
||||
await licenseService.updateSubscriptionOrgMemberCount(identityOrgMembership.scopeOrgId);
|
||||
return { ...deletedIdentity, orgId: identityOrgMembership.scopeOrgId };
|
||||
|
||||
@@ -40,9 +40,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
||||
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
|
||||
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||
void queryBuilder
|
||||
.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`)
|
||||
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||
void queryBuilder.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`);
|
||||
})
|
||||
.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId)
|
||||
.where(`${TableName.Membership}.actorIdentityId`, identityId)
|
||||
@@ -92,6 +90,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
||||
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
||||
db.ref("id").withSchema(TableName.Identity).as("identityId"),
|
||||
db.ref("orgId").withSchema(TableName.Identity).as("identityOrgId"),
|
||||
db.ref("projectId").withSchema(TableName.Identity).as("identityProjectId"),
|
||||
db.ref("hasDeleteProtection").withSchema(TableName.Identity).as("identityHasDeleteProtection"),
|
||||
|
||||
db.ref("slug").withSchema(TableName.Role).as("roleSlug"),
|
||||
@@ -134,6 +133,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
||||
const {
|
||||
identityId: actorIdentityId,
|
||||
identityOrgId,
|
||||
identityProjectId,
|
||||
identityHasDeleteProtection,
|
||||
identityName,
|
||||
uaId,
|
||||
@@ -155,7 +155,8 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
||||
name: identityName,
|
||||
id: actorIdentityId,
|
||||
hasDeleteProtection: identityHasDeleteProtection,
|
||||
identityOrgId,
|
||||
orgId: identityOrgId,
|
||||
projectId: identityProjectId,
|
||||
authMethods: buildAuthMethods({
|
||||
uaId,
|
||||
awsId,
|
||||
@@ -213,7 +214,10 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
||||
]
|
||||
});
|
||||
|
||||
return data?.[0];
|
||||
const el = data?.[0];
|
||||
if (!el) return el;
|
||||
|
||||
return { ...el, identity: { ...el.identity, metadata: el.metadata } };
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "MembershipGetByIdentityId" });
|
||||
}
|
||||
@@ -281,6 +285,8 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
||||
.select(
|
||||
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
||||
db.ref("id").withSchema(TableName.Identity).as("identityId"),
|
||||
db.ref("orgId").withSchema(TableName.Identity).as("identityOrgId"),
|
||||
db.ref("projectId").withSchema(TableName.Identity).as("identityProjectId"),
|
||||
db.ref("hasDeleteProtection").withSchema(TableName.Identity).as("identityHasDeleteProtection"),
|
||||
|
||||
db.ref("slug").withSchema(TableName.Role).as("roleSlug"),
|
||||
@@ -310,13 +316,22 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
||||
data: docs,
|
||||
key: "id",
|
||||
parentMapper: (el) => {
|
||||
const { identityId: actorIdentityId, identityHasDeleteProtection, identityName } = el;
|
||||
const {
|
||||
identityId: actorIdentityId,
|
||||
identityHasDeleteProtection,
|
||||
identityName,
|
||||
identityProjectId,
|
||||
identityOrgId
|
||||
} = el;
|
||||
return {
|
||||
...MembershipsSchema.parse(el),
|
||||
identityId: actorIdentityId,
|
||||
identity: {
|
||||
name: identityName,
|
||||
id: actorIdentityId,
|
||||
hasDeleteProtection: identityHasDeleteProtection
|
||||
hasDeleteProtection: identityHasDeleteProtection,
|
||||
orgId: identityOrgId,
|
||||
projectId: identityProjectId
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -356,14 +371,20 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
// this right now only support sub organization
|
||||
const listAvailableIdentities = async (orgId: string, rootOrgId: string) => {
|
||||
const listAvailableIdentities = async (scopeData: AccessScopeData, rootOrgId: string) => {
|
||||
// TODO (akhil/scott): need to implement filters
|
||||
|
||||
try {
|
||||
const usersConnectedToOrg = db
|
||||
const identitiesConnectedToOrg = db
|
||||
.replicaNode()(TableName.Membership)
|
||||
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
|
||||
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
|
||||
.where(`${TableName.Membership}.scopeOrgId`, orgId)
|
||||
.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId)
|
||||
.where(`${TableName.Membership}.scope`, scopeData.scope)
|
||||
.where((qb) => {
|
||||
if (scopeData.scope === AccessScope.Project) {
|
||||
void qb.where(`${TableName.Membership}.scopeProjectId`, scopeData.projectId);
|
||||
}
|
||||
})
|
||||
.select("actorIdentityId");
|
||||
|
||||
const docs = await db
|
||||
@@ -371,8 +392,16 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
||||
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Membership}.actorIdentityId`)
|
||||
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
|
||||
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
|
||||
.where(`${TableName.Membership}.scopeOrgId`, rootOrgId)
|
||||
.whereNotIn(`${TableName.Membership}.actorIdentityId`, usersConnectedToOrg)
|
||||
.whereNull(`${TableName.Identity}.projectId`)
|
||||
.where((qb) => {
|
||||
// if sub org pick from root and if project pick from org of project
|
||||
if (scopeData.scope === AccessScope.Organization) {
|
||||
void qb.where(`${TableName.Membership}.scopeOrgId`, rootOrgId);
|
||||
} else {
|
||||
void qb.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId);
|
||||
}
|
||||
})
|
||||
.whereNotIn(`${TableName.Membership}.actorIdentityId`, identitiesConnectedToOrg)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.Identity),
|
||||
db.ref("name").withSchema(TableName.Identity),
|
||||
|
||||
@@ -55,7 +55,8 @@ export const membershipIdentityServiceFactory = ({
|
||||
[AccessScope.Project]: newProjectMembershipIdentityFactory({
|
||||
membershipIdentityDAL,
|
||||
orgDAL,
|
||||
permissionService
|
||||
permissionService,
|
||||
identityDAL
|
||||
}),
|
||||
[AccessScope.Namespace]: newNamespaceMembershipIdentityFactory({})
|
||||
};
|
||||
@@ -298,7 +299,7 @@ export const membershipIdentityServiceFactory = ({
|
||||
const { scopeData } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
await factory.onListMembershipIdentityGuard(dto);
|
||||
const listFilter = await factory.onListMembershipIdentityGuard(dto);
|
||||
const memberships = await membershipIdentityDAL.findIdentities({
|
||||
scopeData,
|
||||
filter: {
|
||||
@@ -316,7 +317,7 @@ export const membershipIdentityServiceFactory = ({
|
||||
: undefined
|
||||
}
|
||||
});
|
||||
return memberships;
|
||||
return { ...memberships, data: memberships.data.filter((el) => listFilter({ identityId: el.identity.id })) };
|
||||
};
|
||||
|
||||
const getMembershipByIdentityId = async (dto: TGetMembershipIdentityByIdentityIdDTO) => {
|
||||
@@ -339,13 +340,10 @@ export const membershipIdentityServiceFactory = ({
|
||||
|
||||
await factory.onListMembershipIdentityGuard(dto);
|
||||
|
||||
const organizationDetails = await orgDAL.findById(dto.scopeData.orgId);
|
||||
if (!organizationDetails.rootOrgId) return { identities: [] };
|
||||
if (scopeData.scope !== AccessScope.Project && dto.permission.rootOrgId === dto.permission.orgId)
|
||||
return { identities: [] };
|
||||
|
||||
const identities = await membershipIdentityDAL.listAvailableIdentities(
|
||||
organizationDetails.id,
|
||||
organizationDetails.rootOrgId
|
||||
);
|
||||
const identities = await membershipIdentityDAL.listAvailableIdentities(dto.scopeData, dto.permission.rootOrgId);
|
||||
|
||||
return { identities };
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface TMembershipIdentityScopeFactory {
|
||||
|
||||
onUpdateMembershipIdentityGuard: (arg: TUpdateMembershipIdentityDTO) => Promise<void>;
|
||||
onDeleteMembershipIdentityGuard: (arg: TDeleteMembershipIdentityDTO) => Promise<void>;
|
||||
onListMembershipIdentityGuard: (arg: TListMembershipIdentityDTO) => Promise<void>;
|
||||
onListMembershipIdentityGuard: (arg: TListMembershipIdentityDTO) => Promise<(arg: { identityId: string }) => boolean>;
|
||||
onGetMembershipIdentityByIdentityIdGuard: (arg: TGetMembershipIdentityByIdentityIdDTO) => Promise<void>;
|
||||
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
|
||||
getScopeDatabaseFields: (scope: AccessScopeData) => {
|
||||
|
||||
@@ -60,6 +60,10 @@ export const newOrgMembershipIdentityFactory = ({
|
||||
throw new BadRequestError({ message: "Only identities from parent organization can be invited" });
|
||||
}
|
||||
|
||||
if (identityDetails.projectId) {
|
||||
throw new BadRequestError({ message: "Failed to create organization membership for a project scoped identity" });
|
||||
}
|
||||
|
||||
const permissionRoles = await permissionService.getOrgPermissionByRoles(
|
||||
dto.data.roles.map((el) => el.role),
|
||||
dto.permission.orgId
|
||||
@@ -129,6 +133,11 @@ export const newOrgMembershipIdentityFactory = ({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const identityDetails = await identityDAL.findById(dto.selector.identityId);
|
||||
if (identityDetails.projectId) {
|
||||
throw new BadRequestError({ message: "Failed to create organization membership for a project scoped identity" });
|
||||
}
|
||||
};
|
||||
|
||||
const onDeleteMembershipIdentityGuard: TMembershipIdentityScopeFactory["onDeleteMembershipIdentityGuard"] = async (
|
||||
@@ -153,6 +162,10 @@ export const newOrgMembershipIdentityFactory = ({
|
||||
if (identityDetails.orgId === dto.permission.orgId) {
|
||||
throw new BadRequestError({ message: "Identity cannot exist as orphan" });
|
||||
}
|
||||
|
||||
if (identityDetails.projectId) {
|
||||
throw new BadRequestError({ message: "Failed to create organization membership for a project scoped identity" });
|
||||
}
|
||||
};
|
||||
|
||||
const onListMembershipIdentityGuard: TMembershipIdentityScopeFactory["onListMembershipIdentityGuard"] = async (
|
||||
@@ -167,6 +180,8 @@ export const newOrgMembershipIdentityFactory = ({
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
|
||||
return () => true;
|
||||
};
|
||||
|
||||
const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] =
|
||||
|
||||
+30
-5
@@ -1,4 +1,4 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { AccessScope, ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
|
||||
import {
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
|
||||
import { TMembershipIdentityDALFactory } from "../membership-identity-dal";
|
||||
@@ -19,6 +20,8 @@ import { TMembershipIdentityScopeFactory } from "../membership-identity-types";
|
||||
|
||||
type TProjectMembershipIdentityScopeFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles">;
|
||||
|
||||
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne">;
|
||||
};
|
||||
@@ -26,7 +29,8 @@ type TProjectMembershipIdentityScopeFactoryDep = {
|
||||
export const newProjectMembershipIdentityFactory = ({
|
||||
permissionService,
|
||||
orgDAL,
|
||||
membershipIdentityDAL
|
||||
membershipIdentityDAL,
|
||||
identityDAL
|
||||
}: TProjectMembershipIdentityScopeFactoryDep): TMembershipIdentityScopeFactory => {
|
||||
const getScopeField: TMembershipIdentityScopeFactory["getScopeField"] = (dto) => {
|
||||
if (dto.scope === AccessScope.Project) {
|
||||
@@ -68,6 +72,11 @@ export const newProjectMembershipIdentityFactory = ({
|
||||
if (!orgMembership)
|
||||
throw new BadRequestError({ message: `Identity ${dto.data.identityId} is missing organization membership` });
|
||||
|
||||
const identityDetails = await identityDAL.findById(dto.data.identityId);
|
||||
if (identityDetails.projectId) {
|
||||
throw new BadRequestError({ message: "Failed to create project membership for a project scoped identity" });
|
||||
}
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
|
||||
const permissionRoles = await permissionService.getProjectPermissionByRoles(
|
||||
dto.data.roles.map((el) => el.role),
|
||||
@@ -110,9 +119,14 @@ export const newProjectMembershipIdentityFactory = ({
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
ProjectPermissionSub.Identity
|
||||
subject(ProjectPermissionSub.Identity, { identityId: dto.selector.identityId })
|
||||
);
|
||||
|
||||
const identityDetails = await identityDAL.findById(dto.selector.identityId);
|
||||
if (identityDetails.projectId && identityDetails.projectId !== scope.value) {
|
||||
throw new BadRequestError({ message: "Failed to update project membership for a project scoped identity" });
|
||||
}
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
|
||||
const permissionRoles = await permissionService.getProjectPermissionByRoles(
|
||||
dto.data.roles.filter((el) => el.role !== ProjectMembershipRole.NoAccess).map((el) => el.role),
|
||||
@@ -154,8 +168,13 @@ export const newProjectMembershipIdentityFactory = ({
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Delete,
|
||||
ProjectPermissionSub.Identity
|
||||
subject(ProjectPermissionSub.Identity, { identityId: dto.selector.identityId })
|
||||
);
|
||||
|
||||
const identityDetails = await identityDAL.findById(dto.selector.identityId);
|
||||
if (identityDetails.projectId) {
|
||||
throw new BadRequestError({ message: "Failed to delete project membership for a project scoped identity" });
|
||||
}
|
||||
};
|
||||
|
||||
const onListMembershipIdentityGuard: TMembershipIdentityScopeFactory["onListMembershipIdentityGuard"] = async (
|
||||
@@ -175,6 +194,12 @@ export const newProjectMembershipIdentityFactory = ({
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
ProjectPermissionSub.Identity
|
||||
);
|
||||
|
||||
return (arg) =>
|
||||
permission.can(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId: arg.identityId })
|
||||
);
|
||||
};
|
||||
|
||||
const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] =
|
||||
@@ -191,7 +216,7 @@ export const newProjectMembershipIdentityFactory = ({
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
ProjectPermissionSub.Identity
|
||||
subject(ProjectPermissionSub.Identity, { identityId: dto.selector.identityId })
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
import { UnpackedPermissionSchema, unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
|
||||
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TExternalGroupOrgRoleMappingDALFactory } from "../external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
||||
@@ -33,6 +34,7 @@ type TRoleServiceFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
externalGroupOrgRoleMappingDAL: Pick<TExternalGroupOrgRoleMappingDALFactory, "findOne">;
|
||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "find">;
|
||||
};
|
||||
|
||||
export type TRoleServiceFactory = ReturnType<typeof roleServiceFactory>;
|
||||
@@ -43,7 +45,8 @@ export const roleServiceFactory = ({
|
||||
projectDAL,
|
||||
identityDAL,
|
||||
userDAL,
|
||||
externalGroupOrgRoleMappingDAL
|
||||
externalGroupOrgRoleMappingDAL,
|
||||
membershipRoleDAL
|
||||
}: TRoleServiceFactoryDep) => {
|
||||
const orgRoleFactory = newOrgRoleFactory({
|
||||
permissionService,
|
||||
@@ -137,6 +140,23 @@ export const roleServiceFactory = ({
|
||||
});
|
||||
if (!existingRole) throw new NotFoundError({ message: `Role with ${dto.selector.id} not found` });
|
||||
|
||||
const [roleUsageData] = await membershipRoleDAL.find(
|
||||
{
|
||||
customRoleId: dto.selector.id
|
||||
},
|
||||
{ count: true }
|
||||
);
|
||||
|
||||
if (roleUsageData) {
|
||||
const count = Number.parseInt(roleUsageData.count, 10);
|
||||
if (count > 0) {
|
||||
const plural = count > 1 ? "s" : "";
|
||||
throw new BadRequestError({
|
||||
message: `Role is assigned to ${count} identity membership${plural}. Re-assign membership role${plural} to delete this role.`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const [role] = await roleDAL.delete({
|
||||
id: existingRole.id,
|
||||
[scope.key]: scope.value
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const OnePassSyncDestinationConfigSchema = z.object({
|
||||
vaultId: z.string().trim().min(1, "Vault required").describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.vaultId),
|
||||
valueLabel: z.string().trim().optional().describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.valueLabel)
|
||||
@@ -17,10 +19,12 @@ const OnePassSyncDestinationConfigSchema = z.object({
|
||||
|
||||
const OnePassSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const OnePassSyncSchema = BaseSecretSyncSchema(SecretSync.OnePass, OnePassSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.OnePass),
|
||||
destinationConfig: OnePassSyncDestinationConfigSchema
|
||||
});
|
||||
export const OnePassSyncSchema = BaseSecretSyncSchema(SecretSync.OnePass, OnePassSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.OnePass),
|
||||
destinationConfig: OnePassSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.OnePass] }));
|
||||
|
||||
export const CreateOnePassSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.OnePass,
|
||||
@@ -36,9 +40,11 @@ export const UpdateOnePassSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: OnePassSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const OnePassSyncListItemSchema = z.object({
|
||||
name: z.literal("1Password"),
|
||||
connection: z.literal(AppConnection.OnePass),
|
||||
destination: z.literal(SecretSync.OnePass),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const OnePassSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("1Password"),
|
||||
connection: z.literal(AppConnection.OnePass),
|
||||
destination: z.literal(SecretSync.OnePass),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.OnePass] }));
|
||||
|
||||
+16
-10
@@ -11,6 +11,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const tagFieldCharacterValidator = characterValidator([
|
||||
CharacterType.AlphaNumeric,
|
||||
CharacterType.Spaces,
|
||||
@@ -105,10 +107,12 @@ export const AwsParameterStoreSyncSchema = BaseSecretSyncSchema(
|
||||
SecretSync.AWSParameterStore,
|
||||
AwsParameterStoreSyncOptionsConfig,
|
||||
AwsParameterStoreSyncOptionsSchema
|
||||
).extend({
|
||||
destination: z.literal(SecretSync.AWSParameterStore),
|
||||
destinationConfig: AwsParameterStoreSyncDestinationConfigSchema
|
||||
});
|
||||
)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.AWSParameterStore),
|
||||
destinationConfig: AwsParameterStoreSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AWSParameterStore] }));
|
||||
|
||||
export const CreateAwsParameterStoreSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.AWSParameterStore,
|
||||
@@ -126,9 +130,11 @@ export const UpdateAwsParameterStoreSyncSchema = GenericUpdateSecretSyncFieldsSc
|
||||
destinationConfig: AwsParameterStoreSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const AwsParameterStoreSyncListItemSchema = z.object({
|
||||
name: z.literal("AWS Parameter Store"),
|
||||
connection: z.literal(AppConnection.AWS),
|
||||
destination: z.literal(SecretSync.AWSParameterStore),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const AwsParameterStoreSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("AWS Parameter Store"),
|
||||
connection: z.literal(AppConnection.AWS),
|
||||
destination: z.literal(SecretSync.AWSParameterStore),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AWSParameterStore] }));
|
||||
|
||||
+16
-10
@@ -12,6 +12,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const AwsSecretsManagerSyncDestinationConfigSchema = z
|
||||
.discriminatedUnion("mappingBehavior", [
|
||||
z.object({
|
||||
@@ -119,10 +121,12 @@ export const AwsSecretsManagerSyncSchema = BaseSecretSyncSchema(
|
||||
SecretSync.AWSSecretsManager,
|
||||
AwsSecretsManagerSyncOptionsConfig,
|
||||
AwsSecretsManagerSyncOptionsSchema
|
||||
).extend({
|
||||
destination: z.literal(SecretSync.AWSSecretsManager),
|
||||
destinationConfig: AwsSecretsManagerSyncDestinationConfigSchema
|
||||
});
|
||||
)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.AWSSecretsManager),
|
||||
destinationConfig: AwsSecretsManagerSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AWSSecretsManager] }));
|
||||
|
||||
export const CreateAwsSecretsManagerSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.AWSSecretsManager,
|
||||
@@ -164,9 +168,11 @@ export const UpdateAwsSecretsManagerSyncSchema = GenericUpdateSecretSyncFieldsSc
|
||||
}
|
||||
});
|
||||
|
||||
export const AwsSecretsManagerSyncListItemSchema = z.object({
|
||||
name: z.literal("AWS Secrets Manager"),
|
||||
connection: z.literal(AppConnection.AWS),
|
||||
destination: z.literal(SecretSync.AWSSecretsManager),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const AwsSecretsManagerSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("AWS Secrets Manager"),
|
||||
connection: z.literal(AppConnection.AWS),
|
||||
destination: z.literal(SecretSync.AWSSecretsManager),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AWSSecretsManager] }));
|
||||
|
||||
+16
-10
@@ -10,6 +10,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const AzureAppConfigurationSyncDestinationConfigSchema = z.object({
|
||||
configurationUrl: z
|
||||
.string()
|
||||
@@ -23,10 +25,12 @@ const AzureAppConfigurationSyncOptionsConfig: TSyncOptionsConfig = { canImportSe
|
||||
export const AzureAppConfigurationSyncSchema = BaseSecretSyncSchema(
|
||||
SecretSync.AzureAppConfiguration,
|
||||
AzureAppConfigurationSyncOptionsConfig
|
||||
).extend({
|
||||
destination: z.literal(SecretSync.AzureAppConfiguration),
|
||||
destinationConfig: AzureAppConfigurationSyncDestinationConfigSchema
|
||||
});
|
||||
)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.AzureAppConfiguration),
|
||||
destinationConfig: AzureAppConfigurationSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureAppConfiguration] }));
|
||||
|
||||
export const CreateAzureAppConfigurationSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.AzureAppConfiguration,
|
||||
@@ -42,9 +46,11 @@ export const UpdateAzureAppConfigurationSyncSchema = GenericUpdateSecretSyncFiel
|
||||
destinationConfig: AzureAppConfigurationSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const AzureAppConfigurationSyncListItemSchema = z.object({
|
||||
name: z.literal("Azure App Configuration"),
|
||||
connection: z.literal(AppConnection.AzureAppConfiguration),
|
||||
destination: z.literal(SecretSync.AzureAppConfiguration),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const AzureAppConfigurationSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Azure App Configuration"),
|
||||
connection: z.literal(AppConnection.AzureAppConfiguration),
|
||||
destination: z.literal(SecretSync.AzureAppConfiguration),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureAppConfiguration] }));
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
export const AzureDevOpsSyncDestinationConfigSchema = z.object({
|
||||
devopsProjectId: z
|
||||
.string()
|
||||
@@ -23,10 +25,12 @@ export const AzureDevOpsSyncDestinationConfigSchema = z.object({
|
||||
|
||||
const AzureDevOpsSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
export const AzureDevOpsSyncSchema = BaseSecretSyncSchema(SecretSync.AzureDevOps, AzureDevOpsSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.AzureDevOps),
|
||||
destinationConfig: AzureDevOpsSyncDestinationConfigSchema
|
||||
});
|
||||
export const AzureDevOpsSyncSchema = BaseSecretSyncSchema(SecretSync.AzureDevOps, AzureDevOpsSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.AzureDevOps),
|
||||
destinationConfig: AzureDevOpsSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureDevOps] }));
|
||||
|
||||
export const CreateAzureDevOpsSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.AzureDevOps,
|
||||
@@ -42,9 +46,11 @@ export const UpdateAzureDevOpsSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: AzureDevOpsSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const AzureDevOpsSyncListItemSchema = z.object({
|
||||
name: z.literal("Azure DevOps"),
|
||||
connection: z.literal(AppConnection.AzureDevOps),
|
||||
destination: z.literal(SecretSync.AzureDevOps),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const AzureDevOpsSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Azure DevOps"),
|
||||
connection: z.literal(AppConnection.AzureDevOps),
|
||||
destination: z.literal(SecretSync.AzureDevOps),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureDevOps] }));
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const AzureKeyVaultSyncDestinationConfigSchema = z.object({
|
||||
vaultBaseUrl: z
|
||||
.string()
|
||||
@@ -20,13 +22,12 @@ const AzureKeyVaultSyncDestinationConfigSchema = z.object({
|
||||
|
||||
const AzureKeyVaultSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const AzureKeyVaultSyncSchema = BaseSecretSyncSchema(
|
||||
SecretSync.AzureKeyVault,
|
||||
AzureKeyVaultSyncOptionsConfig
|
||||
).extend({
|
||||
destination: z.literal(SecretSync.AzureKeyVault),
|
||||
destinationConfig: AzureKeyVaultSyncDestinationConfigSchema
|
||||
});
|
||||
export const AzureKeyVaultSyncSchema = BaseSecretSyncSchema(SecretSync.AzureKeyVault, AzureKeyVaultSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.AzureKeyVault),
|
||||
destinationConfig: AzureKeyVaultSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureKeyVault] }));
|
||||
|
||||
export const CreateAzureKeyVaultSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.AzureKeyVault,
|
||||
@@ -42,9 +43,11 @@ export const UpdateAzureKeyVaultSyncSchema = GenericUpdateSecretSyncFieldsSchema
|
||||
destinationConfig: AzureKeyVaultSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const AzureKeyVaultSyncListItemSchema = z.object({
|
||||
name: z.literal("Azure Key Vault"),
|
||||
connection: z.literal(AppConnection.AzureKeyVault),
|
||||
destination: z.literal(SecretSync.AzureKeyVault),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const AzureKeyVaultSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Azure Key Vault"),
|
||||
connection: z.literal(AppConnection.AzureKeyVault),
|
||||
destination: z.literal(SecretSync.AzureKeyVault),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureKeyVault] }));
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const BitbucketSyncDestinationConfigSchema = z.object({
|
||||
repositorySlug: z.string().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.repositorySlug),
|
||||
environmentId: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.environmentId),
|
||||
@@ -18,10 +20,12 @@ const BitbucketSyncDestinationConfigSchema = z.object({
|
||||
|
||||
const BitbucketSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
export const BitbucketSyncSchema = BaseSecretSyncSchema(SecretSync.Bitbucket, BitbucketSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Bitbucket),
|
||||
destinationConfig: BitbucketSyncDestinationConfigSchema
|
||||
});
|
||||
export const BitbucketSyncSchema = BaseSecretSyncSchema(SecretSync.Bitbucket, BitbucketSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Bitbucket),
|
||||
destinationConfig: BitbucketSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Bitbucket] }));
|
||||
|
||||
export const CreateBitbucketSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Bitbucket,
|
||||
@@ -37,9 +41,11 @@ export const UpdateBitbucketSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: BitbucketSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const BitbucketSyncListItemSchema = z.object({
|
||||
name: z.literal("Bitbucket"),
|
||||
connection: z.literal(AppConnection.Bitbucket),
|
||||
destination: z.literal(SecretSync.Bitbucket),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const BitbucketSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Bitbucket"),
|
||||
connection: z.literal(AppConnection.Bitbucket),
|
||||
destination: z.literal(SecretSync.Bitbucket),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Bitbucket] }));
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const CamundaSyncDestinationConfigSchema = z.object({
|
||||
scope: z.string().trim().min(1, "Camunda scope required").describe(SecretSyncs.DESTINATION_CONFIG.CAMUNDA.scope),
|
||||
clusterUUID: z
|
||||
@@ -20,10 +22,12 @@ const CamundaSyncDestinationConfigSchema = z.object({
|
||||
|
||||
const CamundaSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const CamundaSyncSchema = BaseSecretSyncSchema(SecretSync.Camunda, CamundaSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Camunda),
|
||||
destinationConfig: CamundaSyncDestinationConfigSchema
|
||||
});
|
||||
export const CamundaSyncSchema = BaseSecretSyncSchema(SecretSync.Camunda, CamundaSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Camunda),
|
||||
destinationConfig: CamundaSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Camunda] }));
|
||||
|
||||
export const CreateCamundaSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Camunda,
|
||||
@@ -39,9 +43,11 @@ export const UpdateCamundaSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: CamundaSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const CamundaSyncListItemSchema = z.object({
|
||||
name: z.literal("Camunda"),
|
||||
connection: z.literal(AppConnection.Camunda),
|
||||
destination: z.literal(SecretSync.Camunda),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const CamundaSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Camunda"),
|
||||
connection: z.literal(AppConnection.Camunda),
|
||||
destination: z.literal(SecretSync.Camunda),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Camunda] }));
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const ChecklySyncDestinationConfigSchema = z.object({
|
||||
accountId: z.string().min(1, "Account ID is required").max(255, "Account ID must be less than 255 characters"),
|
||||
accountName: z
|
||||
@@ -26,10 +28,12 @@ const ChecklySyncDestinationConfigSchema = z.object({
|
||||
|
||||
const ChecklySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
export const ChecklySyncSchema = BaseSecretSyncSchema(SecretSync.Checkly, ChecklySyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Checkly),
|
||||
destinationConfig: ChecklySyncDestinationConfigSchema
|
||||
});
|
||||
export const ChecklySyncSchema = BaseSecretSyncSchema(SecretSync.Checkly, ChecklySyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Checkly),
|
||||
destinationConfig: ChecklySyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Checkly] }));
|
||||
|
||||
export const CreateChecklySyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Checkly,
|
||||
@@ -45,9 +49,11 @@ export const UpdateChecklySyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: ChecklySyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const ChecklySyncListItemSchema = z.object({
|
||||
name: z.literal("Checkly"),
|
||||
connection: z.literal(AppConnection.Checkly),
|
||||
destination: z.literal(SecretSync.Checkly),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const ChecklySyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Checkly"),
|
||||
connection: z.literal(AppConnection.Checkly),
|
||||
destination: z.literal(SecretSync.Checkly),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Checkly] }));
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const CloudflarePagesSyncDestinationConfigSchema = z.object({
|
||||
projectName: z
|
||||
.string()
|
||||
@@ -26,10 +28,12 @@ const CloudflarePagesSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets:
|
||||
export const CloudflarePagesSyncSchema = BaseSecretSyncSchema(
|
||||
SecretSync.CloudflarePages,
|
||||
CloudflarePagesSyncOptionsConfig
|
||||
).extend({
|
||||
destination: z.literal(SecretSync.CloudflarePages),
|
||||
destinationConfig: CloudflarePagesSyncDestinationConfigSchema
|
||||
});
|
||||
)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.CloudflarePages),
|
||||
destinationConfig: CloudflarePagesSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.CloudflarePages] }));
|
||||
|
||||
export const CreateCloudflarePagesSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.CloudflarePages,
|
||||
@@ -45,9 +49,11 @@ export const UpdateCloudflarePagesSyncSchema = GenericUpdateSecretSyncFieldsSche
|
||||
destinationConfig: CloudflarePagesSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const CloudflarePagesSyncListItemSchema = z.object({
|
||||
name: z.literal("Cloudflare Pages"),
|
||||
connection: z.literal(AppConnection.Cloudflare),
|
||||
destination: z.literal(SecretSync.CloudflarePages),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const CloudflarePagesSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Cloudflare Pages"),
|
||||
connection: z.literal(AppConnection.Cloudflare),
|
||||
destination: z.literal(SecretSync.CloudflarePages),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.CloudflarePages] }));
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const CloudflareWorkersSyncDestinationConfigSchema = z.object({
|
||||
scriptId: z
|
||||
.string()
|
||||
@@ -28,10 +30,12 @@ const CloudflareWorkersSyncOptionsConfig: TSyncOptionsConfig = { canImportSecret
|
||||
export const CloudflareWorkersSyncSchema = BaseSecretSyncSchema(
|
||||
SecretSync.CloudflareWorkers,
|
||||
CloudflareWorkersSyncOptionsConfig
|
||||
).extend({
|
||||
destination: z.literal(SecretSync.CloudflareWorkers),
|
||||
destinationConfig: CloudflareWorkersSyncDestinationConfigSchema
|
||||
});
|
||||
)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.CloudflareWorkers),
|
||||
destinationConfig: CloudflareWorkersSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.CloudflareWorkers] }));
|
||||
|
||||
export const CreateCloudflareWorkersSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.CloudflareWorkers,
|
||||
@@ -47,9 +51,11 @@ export const UpdateCloudflareWorkersSyncSchema = GenericUpdateSecretSyncFieldsSc
|
||||
destinationConfig: CloudflareWorkersSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const CloudflareWorkersSyncListItemSchema = z.object({
|
||||
name: z.literal("Cloudflare Workers"),
|
||||
connection: z.literal(AppConnection.Cloudflare),
|
||||
destination: z.literal(SecretSync.CloudflareWorkers),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const CloudflareWorkersSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Cloudflare Workers"),
|
||||
connection: z.literal(AppConnection.Cloudflare),
|
||||
destination: z.literal(SecretSync.CloudflareWorkers),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.CloudflareWorkers] }));
|
||||
|
||||
@@ -10,16 +10,20 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const DatabricksSyncDestinationConfigSchema = z.object({
|
||||
scope: z.string().trim().min(1, "Databricks scope required").describe(SecretSyncs.DESTINATION_CONFIG.DATABRICKS.scope)
|
||||
});
|
||||
|
||||
const DatabricksSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
export const DatabricksSyncSchema = BaseSecretSyncSchema(SecretSync.Databricks, DatabricksSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Databricks),
|
||||
destinationConfig: DatabricksSyncDestinationConfigSchema
|
||||
});
|
||||
export const DatabricksSyncSchema = BaseSecretSyncSchema(SecretSync.Databricks, DatabricksSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Databricks),
|
||||
destinationConfig: DatabricksSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Databricks] }));
|
||||
|
||||
export const CreateDatabricksSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Databricks,
|
||||
@@ -35,9 +39,11 @@ export const UpdateDatabricksSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: DatabricksSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const DatabricksSyncListItemSchema = z.object({
|
||||
name: z.literal("Databricks"),
|
||||
connection: z.literal(AppConnection.Databricks),
|
||||
destination: z.literal(SecretSync.Databricks),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const DatabricksSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Databricks"),
|
||||
connection: z.literal(AppConnection.Databricks),
|
||||
destination: z.literal(SecretSync.Databricks),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Databricks] }));
|
||||
|
||||
+16
-10
@@ -9,6 +9,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const DigitalOceanAppPlatformSyncDestinationConfigSchema = z.object({
|
||||
appId: z.string().min(1, "Account ID is required").max(255, "Account ID must be less than 255 characters"),
|
||||
appName: z.string().min(1, "Account Name is required").max(255, "Account Name must be less than 255 characters")
|
||||
@@ -19,10 +21,12 @@ const DigitalOceanAppPlatformSyncOptionsConfig: TSyncOptionsConfig = { canImport
|
||||
export const DigitalOceanAppPlatformSyncSchema = BaseSecretSyncSchema(
|
||||
SecretSync.DigitalOceanAppPlatform,
|
||||
DigitalOceanAppPlatformSyncOptionsConfig
|
||||
).extend({
|
||||
destination: z.literal(SecretSync.DigitalOceanAppPlatform),
|
||||
destinationConfig: DigitalOceanAppPlatformSyncDestinationConfigSchema
|
||||
});
|
||||
)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.DigitalOceanAppPlatform),
|
||||
destinationConfig: DigitalOceanAppPlatformSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.DigitalOceanAppPlatform] }));
|
||||
|
||||
export const CreateDigitalOceanAppPlatformSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.DigitalOceanAppPlatform,
|
||||
@@ -38,9 +42,11 @@ export const UpdateDigitalOceanAppPlatformSyncSchema = GenericUpdateSecretSyncFi
|
||||
destinationConfig: DigitalOceanAppPlatformSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const DigitalOceanAppPlatformSyncListItemSchema = z.object({
|
||||
name: z.literal("Digital Ocean App Platform"),
|
||||
connection: z.literal(AppConnection.DigitalOcean),
|
||||
destination: z.literal(SecretSync.DigitalOceanAppPlatform),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const DigitalOceanAppPlatformSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Digital Ocean App Platform"),
|
||||
connection: z.literal(AppConnection.DigitalOcean),
|
||||
destination: z.literal(SecretSync.DigitalOceanAppPlatform),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.DigitalOceanAppPlatform] }));
|
||||
|
||||
@@ -10,16 +10,20 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const FlyioSyncDestinationConfigSchema = z.object({
|
||||
appId: z.string().trim().min(1, "App required").max(255).describe(SecretSyncs.DESTINATION_CONFIG.FLYIO.appId)
|
||||
});
|
||||
|
||||
const FlyioSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
export const FlyioSyncSchema = BaseSecretSyncSchema(SecretSync.Flyio, FlyioSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Flyio),
|
||||
destinationConfig: FlyioSyncDestinationConfigSchema
|
||||
});
|
||||
export const FlyioSyncSchema = BaseSecretSyncSchema(SecretSync.Flyio, FlyioSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Flyio),
|
||||
destinationConfig: FlyioSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Flyio] }));
|
||||
|
||||
export const CreateFlyioSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Flyio,
|
||||
@@ -35,9 +39,11 @@ export const UpdateFlyioSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: FlyioSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const FlyioSyncListItemSchema = z.object({
|
||||
name: z.literal("Fly.io"),
|
||||
connection: z.literal(AppConnection.Flyio),
|
||||
destination: z.literal(SecretSync.Flyio),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const FlyioSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Fly.io"),
|
||||
connection: z.literal(AppConnection.Flyio),
|
||||
destination: z.literal(SecretSync.Flyio),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Flyio] }));
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SecretSync } from "../secret-sync-enums";
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
import { GCPSecretManagerLocation, GcpSyncScope } from "./gcp-sync-enums";
|
||||
|
||||
const GcpSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
@@ -38,10 +39,12 @@ const GcpSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
||||
)
|
||||
]);
|
||||
|
||||
export const GcpSyncSchema = BaseSecretSyncSchema(SecretSync.GCPSecretManager, GcpSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.GCPSecretManager),
|
||||
destinationConfig: GcpSyncDestinationConfigSchema
|
||||
});
|
||||
export const GcpSyncSchema = BaseSecretSyncSchema(SecretSync.GCPSecretManager, GcpSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.GCPSecretManager),
|
||||
destinationConfig: GcpSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GCPSecretManager] }));
|
||||
|
||||
export const CreateGcpSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.GCPSecretManager,
|
||||
@@ -57,9 +60,11 @@ export const UpdateGcpSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: GcpSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const GcpSyncListItemSchema = z.object({
|
||||
name: z.literal("GCP Secret Manager"),
|
||||
connection: z.literal(AppConnection.GCP),
|
||||
destination: z.literal(SecretSync.GCPSecretManager),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const GcpSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("GCP Secret Manager"),
|
||||
connection: z.literal(AppConnection.GCP),
|
||||
destination: z.literal(SecretSync.GCPSecretManager),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GCPSecretManager] }));
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const GitHubSyncDestinationConfigSchema = z
|
||||
.discriminatedUnion("scope", [
|
||||
z.object({
|
||||
@@ -55,10 +57,12 @@ const GitHubSyncDestinationConfigSchema = z
|
||||
|
||||
const GitHubSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
export const GitHubSyncSchema = BaseSecretSyncSchema(SecretSync.GitHub, GitHubSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.GitHub),
|
||||
destinationConfig: GitHubSyncDestinationConfigSchema
|
||||
});
|
||||
export const GitHubSyncSchema = BaseSecretSyncSchema(SecretSync.GitHub, GitHubSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.GitHub),
|
||||
destinationConfig: GitHubSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GitHub] }));
|
||||
|
||||
export const CreateGitHubSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.GitHub,
|
||||
@@ -74,9 +78,11 @@ export const UpdateGitHubSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: GitHubSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const GitHubSyncListItemSchema = z.object({
|
||||
name: z.literal("GitHub"),
|
||||
connection: z.literal(AppConnection.GitHub),
|
||||
destination: z.literal(SecretSync.GitHub),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const GitHubSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("GitHub"),
|
||||
connection: z.literal(AppConnection.GitHub),
|
||||
destination: z.literal(SecretSync.GitHub),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GitHub] }));
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
import { GitLabSyncScope } from "./gitlab-sync-enums";
|
||||
|
||||
const GitLabSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
||||
@@ -70,10 +71,12 @@ const GitLabSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
||||
|
||||
const GitLabSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
export const GitLabSyncSchema = BaseSecretSyncSchema(SecretSync.GitLab, GitLabSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.GitLab),
|
||||
destinationConfig: GitLabSyncDestinationConfigSchema
|
||||
});
|
||||
export const GitLabSyncSchema = BaseSecretSyncSchema(SecretSync.GitLab, GitLabSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.GitLab),
|
||||
destinationConfig: GitLabSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GitLab] }));
|
||||
|
||||
export const CreateGitLabSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.GitLab,
|
||||
@@ -89,9 +92,11 @@ export const UpdateGitLabSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: GitLabSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const GitLabSyncListItemSchema = z.object({
|
||||
name: z.literal("GitLab"),
|
||||
connection: z.literal(AppConnection.GitLab),
|
||||
destination: z.literal(SecretSync.GitLab),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const GitLabSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("GitLab"),
|
||||
connection: z.literal(AppConnection.GitLab),
|
||||
destination: z.literal(SecretSync.GitLab),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GitLab] }));
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const HCVaultSyncDestinationConfigSchema = z.object({
|
||||
mount: z
|
||||
.string()
|
||||
@@ -33,10 +35,12 @@ const HCVaultSyncDestinationConfigSchema = z.object({
|
||||
|
||||
const HCVaultSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const HCVaultSyncSchema = BaseSecretSyncSchema(SecretSync.HCVault, HCVaultSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.HCVault),
|
||||
destinationConfig: HCVaultSyncDestinationConfigSchema
|
||||
});
|
||||
export const HCVaultSyncSchema = BaseSecretSyncSchema(SecretSync.HCVault, HCVaultSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.HCVault),
|
||||
destinationConfig: HCVaultSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.HCVault] }));
|
||||
|
||||
export const CreateHCVaultSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.HCVault,
|
||||
@@ -52,9 +56,11 @@ export const UpdateHCVaultSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: HCVaultSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const HCVaultSyncListItemSchema = z.object({
|
||||
name: z.literal("Hashicorp Vault"),
|
||||
connection: z.literal(AppConnection.HCVault),
|
||||
destination: z.literal(SecretSync.HCVault),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const HCVaultSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Hashicorp Vault"),
|
||||
connection: z.literal(AppConnection.HCVault),
|
||||
destination: z.literal(SecretSync.HCVault),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.HCVault] }));
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const HerokuSyncDestinationConfigSchema = z.object({
|
||||
app: z.string().trim().min(1, "App required").describe(SecretSyncs.DESTINATION_CONFIG.HEROKU.app),
|
||||
appName: z.string().trim().min(1, "App name required").describe(SecretSyncs.DESTINATION_CONFIG.HEROKU.appName)
|
||||
@@ -17,10 +19,12 @@ const HerokuSyncDestinationConfigSchema = z.object({
|
||||
|
||||
const HerokuSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const HerokuSyncSchema = BaseSecretSyncSchema(SecretSync.Heroku, HerokuSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Heroku),
|
||||
destinationConfig: HerokuSyncDestinationConfigSchema
|
||||
});
|
||||
export const HerokuSyncSchema = BaseSecretSyncSchema(SecretSync.Heroku, HerokuSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Heroku),
|
||||
destinationConfig: HerokuSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Heroku] }));
|
||||
|
||||
export const CreateHerokuSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Heroku,
|
||||
@@ -36,9 +40,11 @@ export const UpdateHerokuSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: HerokuSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const HerokuSyncListItemSchema = z.object({
|
||||
name: z.literal("Heroku"),
|
||||
connection: z.literal(AppConnection.Heroku),
|
||||
destination: z.literal(SecretSync.Heroku),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const HerokuSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Heroku"),
|
||||
connection: z.literal(AppConnection.Heroku),
|
||||
destination: z.literal(SecretSync.Heroku),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Heroku] }));
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const HumanitecSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
||||
z.object({
|
||||
scope: z.literal(HumanitecSyncScope.Application).describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.scope),
|
||||
@@ -27,10 +29,12 @@ const HumanitecSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
||||
|
||||
const HumanitecSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
export const HumanitecSyncSchema = BaseSecretSyncSchema(SecretSync.Humanitec, HumanitecSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Humanitec),
|
||||
destinationConfig: HumanitecSyncDestinationConfigSchema
|
||||
});
|
||||
export const HumanitecSyncSchema = BaseSecretSyncSchema(SecretSync.Humanitec, HumanitecSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Humanitec),
|
||||
destinationConfig: HumanitecSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Humanitec] }));
|
||||
|
||||
export const CreateHumanitecSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Humanitec,
|
||||
@@ -46,9 +50,11 @@ export const UpdateHumanitecSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: HumanitecSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const HumanitecSyncListItemSchema = z.object({
|
||||
name: z.literal("Humanitec"),
|
||||
connection: z.literal(AppConnection.Humanitec),
|
||||
destination: z.literal(SecretSync.Humanitec),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const HumanitecSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Humanitec"),
|
||||
connection: z.literal(AppConnection.Humanitec),
|
||||
destination: z.literal(SecretSync.Humanitec),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Humanitec] }));
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const slugValidator = (val: string) => {
|
||||
return new RE2("^[a-z0-9.-]+$").test(val) && !new RE2(".[-]$").test(val);
|
||||
};
|
||||
@@ -38,13 +40,12 @@ const LaravelForgeSyncDestinationConfigSchema = z.object({
|
||||
|
||||
const LaravelForgeSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const LaravelForgeSyncSchema = BaseSecretSyncSchema(
|
||||
SecretSync.LaravelForge,
|
||||
LaravelForgeSyncOptionsConfig
|
||||
).extend({
|
||||
destination: z.literal(SecretSync.LaravelForge),
|
||||
destinationConfig: LaravelForgeSyncDestinationConfigSchema
|
||||
});
|
||||
export const LaravelForgeSyncSchema = BaseSecretSyncSchema(SecretSync.LaravelForge, LaravelForgeSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.LaravelForge),
|
||||
destinationConfig: LaravelForgeSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.LaravelForge] }));
|
||||
|
||||
export const CreateLaravelForgeSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.LaravelForge,
|
||||
@@ -60,9 +61,11 @@ export const UpdateLaravelForgeSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: LaravelForgeSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const LaravelForgeSyncListItemSchema = z.object({
|
||||
name: z.literal("Laravel Forge"),
|
||||
connection: z.literal(AppConnection.LaravelForge),
|
||||
destination: z.literal(SecretSync.LaravelForge),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const LaravelForgeSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Laravel Forge"),
|
||||
connection: z.literal(AppConnection.LaravelForge),
|
||||
destination: z.literal(SecretSync.LaravelForge),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.LaravelForge] }));
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
import { NetlifySyncContext } from "./netlify-sync-constants";
|
||||
|
||||
const NetlifySyncDestinationConfigSchema = z.object({
|
||||
@@ -41,10 +42,12 @@ const NetlifySyncDestinationConfigSchema = z.object({
|
||||
|
||||
const NetlifySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const NetlifySyncSchema = BaseSecretSyncSchema(SecretSync.Netlify, NetlifySyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Netlify),
|
||||
destinationConfig: NetlifySyncDestinationConfigSchema
|
||||
});
|
||||
export const NetlifySyncSchema = BaseSecretSyncSchema(SecretSync.Netlify, NetlifySyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Netlify),
|
||||
destinationConfig: NetlifySyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Netlify] }));
|
||||
|
||||
export const CreateNetlifySyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Netlify,
|
||||
@@ -60,9 +63,11 @@ export const UpdateNetlifySyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: NetlifySyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const NetlifySyncListItemSchema = z.object({
|
||||
name: z.literal("Netlify"),
|
||||
connection: z.literal(AppConnection.Netlify),
|
||||
destination: z.literal(SecretSync.Netlify),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const NetlifySyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Netlify"),
|
||||
connection: z.literal(AppConnection.Netlify),
|
||||
destination: z.literal(SecretSync.Netlify),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Netlify] }));
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const NorthflankSyncDestinationConfigSchema = z.object({
|
||||
projectId: z
|
||||
.string()
|
||||
@@ -27,10 +29,12 @@ const NorthflankSyncDestinationConfigSchema = z.object({
|
||||
|
||||
const NorthflankSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const NorthflankSyncSchema = BaseSecretSyncSchema(SecretSync.Northflank, NorthflankSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Northflank),
|
||||
destinationConfig: NorthflankSyncDestinationConfigSchema
|
||||
});
|
||||
export const NorthflankSyncSchema = BaseSecretSyncSchema(SecretSync.Northflank, NorthflankSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Northflank),
|
||||
destinationConfig: NorthflankSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Northflank] }));
|
||||
|
||||
export const CreateNorthflankSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Northflank,
|
||||
@@ -46,9 +50,11 @@ export const UpdateNorthflankSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: NorthflankSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const NorthflankSyncListItemSchema = z.object({
|
||||
name: z.literal("Northflank"),
|
||||
connection: z.literal(AppConnection.Northflank),
|
||||
destination: z.literal(SecretSync.Northflank),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const NorthflankSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Northflank"),
|
||||
connection: z.literal(AppConnection.Northflank),
|
||||
destination: z.literal(SecretSync.Northflank),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Northflank] }));
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const RailwaySyncDestinationConfigSchema = z.object({
|
||||
projectId: z
|
||||
.string()
|
||||
@@ -29,10 +31,12 @@ const RailwaySyncDestinationConfigSchema = z.object({
|
||||
|
||||
const RailwaySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const RailwaySyncSchema = BaseSecretSyncSchema(SecretSync.Railway, RailwaySyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Railway),
|
||||
destinationConfig: RailwaySyncDestinationConfigSchema
|
||||
});
|
||||
export const RailwaySyncSchema = BaseSecretSyncSchema(SecretSync.Railway, RailwaySyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Railway),
|
||||
destinationConfig: RailwaySyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Railway] }));
|
||||
|
||||
export const CreateRailwaySyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Railway,
|
||||
@@ -48,9 +52,11 @@ export const UpdateRailwaySyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: RailwaySyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const RailwaySyncListItemSchema = z.object({
|
||||
name: z.literal("Railway"),
|
||||
connection: z.literal(AppConnection.Railway),
|
||||
destination: z.literal(SecretSync.Railway),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const RailwaySyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Railway"),
|
||||
connection: z.literal(AppConnection.Railway),
|
||||
destination: z.literal(SecretSync.Railway),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Railway] }));
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
import { RenderSyncScope, RenderSyncType } from "./render-sync-enums";
|
||||
|
||||
const RenderSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
||||
@@ -38,10 +39,12 @@ export const RenderSyncSchema = BaseSecretSyncSchema(
|
||||
SecretSync.Render,
|
||||
RenderSyncOptionsConfig,
|
||||
RenderSyncOptionsSchema
|
||||
).extend({
|
||||
destination: z.literal(SecretSync.Render),
|
||||
destinationConfig: RenderSyncDestinationConfigSchema
|
||||
});
|
||||
)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Render),
|
||||
destinationConfig: RenderSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Render] }));
|
||||
|
||||
export const CreateRenderSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Render,
|
||||
@@ -59,9 +62,11 @@ export const UpdateRenderSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: RenderSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const RenderSyncListItemSchema = z.object({
|
||||
name: z.literal("Render"),
|
||||
connection: z.literal(AppConnection.Render),
|
||||
destination: z.literal(SecretSync.Render),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
export const RenderSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Render"),
|
||||
connection: z.literal(AppConnection.Render),
|
||||
destination: z.literal(SecretSync.Render),
|
||||
canImportSecrets: z.literal(true)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Render] }));
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
const SupabaseSyncDestinationConfigSchema = z.object({
|
||||
projectId: z.string().max(255).min(1, "Project ID is required"),
|
||||
projectName: z.string().max(255).min(1, "Project Name is required")
|
||||
@@ -16,10 +18,12 @@ const SupabaseSyncDestinationConfigSchema = z.object({
|
||||
|
||||
const SupabaseSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
export const SupabaseSyncSchema = BaseSecretSyncSchema(SecretSync.Supabase, SupabaseSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Supabase),
|
||||
destinationConfig: SupabaseSyncDestinationConfigSchema
|
||||
});
|
||||
export const SupabaseSyncSchema = BaseSecretSyncSchema(SecretSync.Supabase, SupabaseSyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.Supabase),
|
||||
destinationConfig: SupabaseSyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Supabase] }));
|
||||
|
||||
export const CreateSupabaseSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.Supabase,
|
||||
@@ -35,9 +39,11 @@ export const UpdateSupabaseSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
destinationConfig: SupabaseSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const SupabaseSyncListItemSchema = z.object({
|
||||
name: z.literal("Supabase"),
|
||||
connection: z.literal(AppConnection.Supabase),
|
||||
destination: z.literal(SecretSync.Supabase),
|
||||
canImportSecrets: z.literal(false)
|
||||
});
|
||||
export const SupabaseSyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Supabase"),
|
||||
connection: z.literal(AppConnection.Supabase),
|
||||
destination: z.literal(SecretSync.Supabase),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Supabase] }));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user