Compare commits
16 Commits
infisical/
...
daniel/cli
Author | SHA1 | Date | |
---|---|---|---|
22f32e060b | |||
b4f26aac25 | |||
b634a6c371 | |||
080ae5ce6f | |||
3b28e946cf | |||
4db82e37c1 | |||
3a8789af76 | |||
79ebfc92e9 | |||
ffca4aa054 | |||
52b3f7e8c8 | |||
4f26b43789 | |||
4817eb2fc6 | |||
f45c917922 | |||
debef510e4 | |||
14cc21787d | |||
f551806737 |
@ -5,6 +5,7 @@ import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-ro
|
||||
import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-rotation-router";
|
||||
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
||||
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
||||
import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router";
|
||||
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
||||
|
||||
export * from "./secret-rotation-v2-router";
|
||||
@ -15,6 +16,7 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
|
||||
> = {
|
||||
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
||||
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
||||
[SecretRotation.MySqlCredentials]: registerMySqlCredentialsRotationRouter,
|
||||
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
||||
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
||||
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
||||
|
@ -0,0 +1,19 @@
|
||||
import {
|
||||
CreateMySqlCredentialsRotationSchema,
|
||||
MySqlCredentialsRotationSchema,
|
||||
UpdateMySqlCredentialsRotationSchema
|
||||
} from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials";
|
||||
|
||||
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
||||
|
||||
export const registerMySqlCredentialsRotationRouter = async (server: FastifyZodProvider) =>
|
||||
registerSecretRotationEndpoints({
|
||||
type: SecretRotation.MySqlCredentials,
|
||||
server,
|
||||
responseSchema: MySqlCredentialsRotationSchema,
|
||||
createSchema: CreateMySqlCredentialsRotationSchema,
|
||||
updateSchema: UpdateMySqlCredentialsRotationSchema,
|
||||
generatedCredentialsSchema: SqlCredentialsRotationGeneratedCredentialsSchema
|
||||
});
|
@ -6,6 +6,7 @@ import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-
|
||||
import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||
import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||
import { ApiDocsTags, SecretRotations } from "@app/lib/api-docs";
|
||||
@ -16,6 +17,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
||||
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
||||
PostgresCredentialsRotationListItemSchema,
|
||||
MsSqlCredentialsRotationListItemSchema,
|
||||
MySqlCredentialsRotationListItemSchema,
|
||||
Auth0ClientSecretRotationListItemSchema,
|
||||
AzureClientSecretRotationListItemSchema,
|
||||
AwsIamUserSecretRotationListItemSchema,
|
||||
|
@ -0,0 +1,3 @@
|
||||
export * from "./mysql-credentials-rotation-constants";
|
||||
export * from "./mysql-credentials-rotation-schemas";
|
||||
export * from "./mysql-credentials-rotation-types";
|
@ -0,0 +1,23 @@
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const MYSQL_CREDENTIALS_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
||||
name: "MySQL Credentials",
|
||||
type: SecretRotation.MySqlCredentials,
|
||||
connection: AppConnection.MySql,
|
||||
template: {
|
||||
createUserStatement: `-- create user
|
||||
CREATE USER 'infisical_user'@'%' IDENTIFIED BY 'temporary_password';
|
||||
|
||||
-- grant all privileges
|
||||
GRANT ALL PRIVILEGES ON my_database.* TO 'infisical_user'@'%';
|
||||
|
||||
-- apply the privilege changes
|
||||
FLUSH PRIVILEGES;`,
|
||||
secretsMapping: {
|
||||
username: "MYSQL_USERNAME",
|
||||
password: "MYSQL_PASSWORD"
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,41 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import {
|
||||
BaseCreateSecretRotationSchema,
|
||||
BaseSecretRotationSchema,
|
||||
BaseUpdateSecretRotationSchema
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
|
||||
import {
|
||||
SqlCredentialsRotationParametersSchema,
|
||||
SqlCredentialsRotationSecretsMappingSchema,
|
||||
SqlCredentialsRotationTemplateSchema
|
||||
} from "@app/ee/services/secret-rotation-v2/shared/sql-credentials";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const MySqlCredentialsRotationSchema = BaseSecretRotationSchema(SecretRotation.MySqlCredentials).extend({
|
||||
type: z.literal(SecretRotation.MySqlCredentials),
|
||||
parameters: SqlCredentialsRotationParametersSchema,
|
||||
secretsMapping: SqlCredentialsRotationSecretsMappingSchema
|
||||
});
|
||||
|
||||
export const CreateMySqlCredentialsRotationSchema = BaseCreateSecretRotationSchema(
|
||||
SecretRotation.MySqlCredentials
|
||||
).extend({
|
||||
parameters: SqlCredentialsRotationParametersSchema,
|
||||
secretsMapping: SqlCredentialsRotationSecretsMappingSchema
|
||||
});
|
||||
|
||||
export const UpdateMySqlCredentialsRotationSchema = BaseUpdateSecretRotationSchema(
|
||||
SecretRotation.MySqlCredentials
|
||||
).extend({
|
||||
parameters: SqlCredentialsRotationParametersSchema.optional(),
|
||||
secretsMapping: SqlCredentialsRotationSecretsMappingSchema.optional()
|
||||
});
|
||||
|
||||
export const MySqlCredentialsRotationListItemSchema = z.object({
|
||||
name: z.literal("MySQL Credentials"),
|
||||
connection: z.literal(AppConnection.MySql),
|
||||
type: z.literal(SecretRotation.MySqlCredentials),
|
||||
template: SqlCredentialsRotationTemplateSchema
|
||||
});
|
@ -0,0 +1,19 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { TMySqlConnection } from "@app/services/app-connection/mysql";
|
||||
|
||||
import {
|
||||
CreateMySqlCredentialsRotationSchema,
|
||||
MySqlCredentialsRotationListItemSchema,
|
||||
MySqlCredentialsRotationSchema
|
||||
} from "./mysql-credentials-rotation-schemas";
|
||||
|
||||
export type TMySqlCredentialsRotation = z.infer<typeof MySqlCredentialsRotationSchema>;
|
||||
|
||||
export type TMySqlCredentialsRotationInput = z.infer<typeof CreateMySqlCredentialsRotationSchema>;
|
||||
|
||||
export type TMySqlCredentialsRotationListItem = z.infer<typeof MySqlCredentialsRotationListItemSchema>;
|
||||
|
||||
export type TMySqlCredentialsRotationWithConnection = TMySqlCredentialsRotation & {
|
||||
connection: TMySqlConnection;
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
export enum SecretRotation {
|
||||
PostgresCredentials = "postgres-credentials",
|
||||
MsSqlCredentials = "mssql-credentials",
|
||||
MySqlCredentials = "mysql-credentials",
|
||||
Auth0ClientSecret = "auth0-client-secret",
|
||||
AzureClientSecret = "azure-client-secret",
|
||||
AwsIamUserSecret = "aws-iam-user-secret",
|
||||
|
@ -9,6 +9,7 @@ import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret"
|
||||
import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret";
|
||||
import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password";
|
||||
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
||||
import { MYSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mysql-credentials";
|
||||
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
||||
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
||||
import { TSecretRotationV2ServiceFactoryDep } from "./secret-rotation-v2-service";
|
||||
@ -23,6 +24,7 @@ import {
|
||||
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
|
||||
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.MySqlCredentials]: MYSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,
|
||||
|
@ -4,6 +4,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
|
||||
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
||||
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
||||
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
||||
[SecretRotation.MySqlCredentials]: "MySQL Credentials",
|
||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
||||
[SecretRotation.AzureClientSecret]: "Azure Client Secret",
|
||||
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
|
||||
@ -13,6 +14,7 @@ export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
||||
[SecretRotation.MySqlCredentials]: AppConnection.MySql,
|
||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
||||
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
||||
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
||||
|
@ -120,6 +120,7 @@ type TRotationFactoryImplementation = TRotationFactory<
|
||||
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
|
||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.MySqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,
|
||||
|
@ -39,6 +39,12 @@ import {
|
||||
TMsSqlCredentialsRotationListItem,
|
||||
TMsSqlCredentialsRotationWithConnection
|
||||
} from "./mssql-credentials";
|
||||
import {
|
||||
TMySqlCredentialsRotation,
|
||||
TMySqlCredentialsRotationInput,
|
||||
TMySqlCredentialsRotationListItem,
|
||||
TMySqlCredentialsRotationWithConnection
|
||||
} from "./mysql-credentials";
|
||||
import {
|
||||
TPostgresCredentialsRotation,
|
||||
TPostgresCredentialsRotationInput,
|
||||
@ -51,6 +57,7 @@ import { SecretRotation } from "./secret-rotation-v2-enums";
|
||||
export type TSecretRotationV2 =
|
||||
| TPostgresCredentialsRotation
|
||||
| TMsSqlCredentialsRotation
|
||||
| TMySqlCredentialsRotation
|
||||
| TAuth0ClientSecretRotation
|
||||
| TAzureClientSecretRotation
|
||||
| TLdapPasswordRotation
|
||||
@ -59,6 +66,7 @@ export type TSecretRotationV2 =
|
||||
export type TSecretRotationV2WithConnection =
|
||||
| TPostgresCredentialsRotationWithConnection
|
||||
| TMsSqlCredentialsRotationWithConnection
|
||||
| TMySqlCredentialsRotationWithConnection
|
||||
| TAuth0ClientSecretRotationWithConnection
|
||||
| TAzureClientSecretRotationWithConnection
|
||||
| TLdapPasswordRotationWithConnection
|
||||
@ -74,6 +82,7 @@ export type TSecretRotationV2GeneratedCredentials =
|
||||
export type TSecretRotationV2Input =
|
||||
| TPostgresCredentialsRotationInput
|
||||
| TMsSqlCredentialsRotationInput
|
||||
| TMySqlCredentialsRotationInput
|
||||
| TAuth0ClientSecretRotationInput
|
||||
| TAzureClientSecretRotationInput
|
||||
| TLdapPasswordRotationInput
|
||||
@ -82,6 +91,7 @@ export type TSecretRotationV2Input =
|
||||
export type TSecretRotationV2ListItem =
|
||||
| TPostgresCredentialsRotationListItem
|
||||
| TMsSqlCredentialsRotationListItem
|
||||
| TMySqlCredentialsRotationListItem
|
||||
| TAuth0ClientSecretRotationListItem
|
||||
| TAzureClientSecretRotationListItem
|
||||
| TLdapPasswordRotationListItem
|
||||
|
@ -4,6 +4,7 @@ import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotatio
|
||||
import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||
import { MySqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||
|
||||
import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret";
|
||||
@ -11,6 +12,7 @@ import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret";
|
||||
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
||||
PostgresCredentialsRotationSchema,
|
||||
MsSqlCredentialsRotationSchema,
|
||||
MySqlCredentialsRotationSchema,
|
||||
Auth0ClientSecretRotationSchema,
|
||||
AzureClientSecretRotationSchema,
|
||||
LdapPasswordRotationSchema,
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { TMsSqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||
import { TMySqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||
import { TPostgresCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||
|
||||
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "./sql-credentials-rotation-schemas";
|
||||
|
||||
export type TSqlCredentialsRotationWithConnection =
|
||||
| TPostgresCredentialsRotationWithConnection
|
||||
| TMsSqlCredentialsRotationWithConnection;
|
||||
| TMsSqlCredentialsRotationWithConnection
|
||||
| TMySqlCredentialsRotationWithConnection;
|
||||
|
||||
export type TSqlCredentialsRotationGeneratedCredentials = z.infer<
|
||||
typeof SqlCredentialsRotationGeneratedCredentialsSchema
|
||||
|
@ -171,6 +171,13 @@ export const getDbSetQuery = (db: TDbProviderClients, variables: { username: str
|
||||
};
|
||||
}
|
||||
|
||||
if (db === TDbProviderClients.MySql) {
|
||||
return {
|
||||
query: `ALTER USER ??@'%' IDENTIFIED BY '${variables.password}'`,
|
||||
variables: [variables.username]
|
||||
};
|
||||
}
|
||||
|
||||
// add more based on client
|
||||
return {
|
||||
query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`,
|
||||
|
@ -43,6 +43,7 @@ import {
|
||||
} from "@app/services/app-connection/humanitec";
|
||||
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
|
||||
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
||||
import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql";
|
||||
import {
|
||||
PostgresConnectionListItemSchema,
|
||||
SanitizedPostgresConnectionSchema
|
||||
@ -75,6 +76,7 @@ const SanitizedAppConnectionSchema = z.union([
|
||||
...SanitizedVercelConnectionSchema.options,
|
||||
...SanitizedPostgresConnectionSchema.options,
|
||||
...SanitizedMsSqlConnectionSchema.options,
|
||||
...SanitizedMySqlConnectionSchema.options,
|
||||
...SanitizedCamundaConnectionSchema.options,
|
||||
...SanitizedAuth0ConnectionSchema.options,
|
||||
...SanitizedHCVaultConnectionSchema.options,
|
||||
@ -98,6 +100,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||
VercelConnectionListItemSchema,
|
||||
PostgresConnectionListItemSchema,
|
||||
MsSqlConnectionListItemSchema,
|
||||
MySqlConnectionListItemSchema,
|
||||
CamundaConnectionListItemSchema,
|
||||
Auth0ConnectionListItemSchema,
|
||||
HCVaultConnectionListItemSchema,
|
||||
|
@ -15,6 +15,7 @@ import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
|
||||
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
||||
import { registerLdapConnectionRouter } from "./ldap-connection-router";
|
||||
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
||||
import { registerMySqlConnectionRouter } from "./mysql-connection-router";
|
||||
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
||||
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
|
||||
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
|
||||
@ -37,6 +38,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
||||
[AppConnection.Vercel]: registerVercelConnectionRouter,
|
||||
[AppConnection.Postgres]: registerPostgresConnectionRouter,
|
||||
[AppConnection.MsSql]: registerMsSqlConnectionRouter,
|
||||
[AppConnection.MySql]: registerMySqlConnectionRouter,
|
||||
[AppConnection.Camunda]: registerCamundaConnectionRouter,
|
||||
[AppConnection.Windmill]: registerWindmillConnectionRouter,
|
||||
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
|
||||
|
@ -0,0 +1,18 @@
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
CreateMySqlConnectionSchema,
|
||||
SanitizedMySqlConnectionSchema,
|
||||
UpdateMySqlConnectionSchema
|
||||
} from "@app/services/app-connection/mysql";
|
||||
|
||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||
|
||||
export const registerMySqlConnectionRouter = async (server: FastifyZodProvider) => {
|
||||
registerAppConnectionEndpoints({
|
||||
app: AppConnection.MySql,
|
||||
server,
|
||||
sanitizedResponseSchema: SanitizedMySqlConnectionSchema,
|
||||
createSchema: CreateMySqlConnectionSchema,
|
||||
updateSchema: UpdateMySqlConnectionSchema
|
||||
});
|
||||
};
|
@ -11,6 +11,7 @@ export enum AppConnection {
|
||||
Vercel = "vercel",
|
||||
Postgres = "postgres",
|
||||
MsSql = "mssql",
|
||||
MySql = "mysql",
|
||||
Camunda = "camunda",
|
||||
Windmill = "windmill",
|
||||
Auth0 = "auth0",
|
||||
|
@ -64,6 +64,8 @@ import {
|
||||
} from "./humanitec";
|
||||
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
|
||||
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
|
||||
import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums";
|
||||
import { getMySqlConnectionListItem } from "./mysql/mysql-connection-fns";
|
||||
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
|
||||
import {
|
||||
getTeamCityConnectionListItem,
|
||||
@ -96,6 +98,7 @@ export const listAppConnectionOptions = () => {
|
||||
getVercelConnectionListItem(),
|
||||
getPostgresConnectionListItem(),
|
||||
getMsSqlConnectionListItem(),
|
||||
getMySqlConnectionListItem(),
|
||||
getCamundaConnectionListItem(),
|
||||
getAzureClientSecretsConnectionListItem(),
|
||||
getWindmillConnectionListItem(),
|
||||
@ -166,6 +169,7 @@ export const validateAppConnectionCredentials = async (
|
||||
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.MySql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
@ -208,6 +212,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
||||
return "API Token";
|
||||
case PostgresConnectionMethod.UsernameAndPassword:
|
||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||
case MySqlConnectionMethod.UsernameAndPassword:
|
||||
return "Username & Password";
|
||||
case WindmillConnectionMethod.AccessToken:
|
||||
case HCVaultConnectionMethod.AccessToken:
|
||||
@ -259,6 +264,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
||||
[AppConnection.Humanitec]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||
[AppConnection.MySql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
|
||||
|
@ -13,6 +13,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
||||
[AppConnection.Vercel]: "Vercel",
|
||||
[AppConnection.Postgres]: "PostgreSQL",
|
||||
[AppConnection.MsSql]: "Microsoft SQL Server",
|
||||
[AppConnection.MySql]: "MySQL",
|
||||
[AppConnection.Camunda]: "Camunda",
|
||||
[AppConnection.Windmill]: "Windmill",
|
||||
[AppConnection.Auth0]: "Auth0",
|
||||
@ -43,5 +44,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
||||
[AppConnection.LDAP]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.TeamCity]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.OCI]: AppConnectionPlanType.Enterprise,
|
||||
[AppConnection.OnePass]: AppConnectionPlanType.Regular
|
||||
[AppConnection.OnePass]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.MySql]: AppConnectionPlanType.Regular
|
||||
};
|
||||
|
@ -55,6 +55,7 @@ import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
|
||||
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
||||
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
||||
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||
import { ValidateMySqlConnectionCredentialsSchema } from "./mysql";
|
||||
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
|
||||
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
|
||||
import { teamcityConnectionService } from "./teamcity/teamcity-connection-service";
|
||||
@ -86,6 +87,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
||||
[AppConnection.Vercel]: ValidateVercelConnectionCredentialsSchema,
|
||||
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
|
||||
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
|
||||
[AppConnection.MySql]: ValidateMySqlConnectionCredentialsSchema,
|
||||
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
|
||||
[AppConnection.AzureClientSecrets]: ValidateAzureClientSecretsConnectionCredentialsSchema,
|
||||
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
||||
|
@ -88,6 +88,7 @@ import {
|
||||
TValidateLdapConnectionCredentialsSchema
|
||||
} from "./ldap";
|
||||
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||
import { TMySqlConnection, TMySqlConnectionInput, TValidateMySqlConnectionCredentialsSchema } from "./mysql";
|
||||
import {
|
||||
TPostgresConnection,
|
||||
TPostgresConnectionInput,
|
||||
@ -130,6 +131,7 @@ export type TAppConnection = { id: string } & (
|
||||
| TVercelConnection
|
||||
| TPostgresConnection
|
||||
| TMsSqlConnection
|
||||
| TMySqlConnection
|
||||
| TCamundaConnection
|
||||
| TAzureClientSecretsConnection
|
||||
| TWindmillConnection
|
||||
@ -143,7 +145,7 @@ export type TAppConnection = { id: string } & (
|
||||
|
||||
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
||||
|
||||
export type TSqlConnection = TPostgresConnection | TMsSqlConnection;
|
||||
export type TSqlConnection = TPostgresConnection | TMsSqlConnection | TMySqlConnection;
|
||||
|
||||
export type TAppConnectionInput = { id: string } & (
|
||||
| TAwsConnectionInput
|
||||
@ -157,6 +159,7 @@ export type TAppConnectionInput = { id: string } & (
|
||||
| TVercelConnectionInput
|
||||
| TPostgresConnectionInput
|
||||
| TMsSqlConnectionInput
|
||||
| TMySqlConnectionInput
|
||||
| TCamundaConnectionInput
|
||||
| TAzureClientSecretsConnectionInput
|
||||
| TWindmillConnectionInput
|
||||
@ -168,7 +171,7 @@ export type TAppConnectionInput = { id: string } & (
|
||||
| TOnePassConnectionInput
|
||||
);
|
||||
|
||||
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput;
|
||||
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput | TMySqlConnectionInput;
|
||||
|
||||
export type TCreateAppConnectionDTO = Pick<
|
||||
TAppConnectionInput,
|
||||
@ -211,6 +214,7 @@ export type TValidateAppConnectionCredentialsSchema =
|
||||
| TValidateHumanitecConnectionCredentialsSchema
|
||||
| TValidatePostgresConnectionCredentialsSchema
|
||||
| TValidateMsSqlConnectionCredentialsSchema
|
||||
| TValidateMySqlConnectionCredentialsSchema
|
||||
| TValidateCamundaConnectionCredentialsSchema
|
||||
| TValidateVercelConnectionCredentialsSchema
|
||||
| TValidateTerraformCloudConnectionCredentialsSchema
|
||||
|
4
backend/src/services/app-connection/mysql/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./mysql-connection-enums";
|
||||
export * from "./mysql-connection-fns";
|
||||
export * from "./mysql-connection-schemas";
|
||||
export * from "./mysql-connection-types";
|
@ -0,0 +1,3 @@
|
||||
export enum MySqlConnectionMethod {
|
||||
UsernameAndPassword = "username-and-password"
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
import { MySqlConnectionMethod } from "./mysql-connection-enums";
|
||||
|
||||
export const getMySqlConnectionListItem = () => {
|
||||
return {
|
||||
name: "MySQL" as const,
|
||||
app: AppConnection.MySql as const,
|
||||
methods: Object.values(MySqlConnectionMethod) as [MySqlConnectionMethod.UsernameAndPassword],
|
||||
supportsPlatformManagement: true as const
|
||||
};
|
||||
};
|
@ -0,0 +1,66 @@
|
||||
import z from "zod";
|
||||
|
||||
import { AppConnections } from "@app/lib/api-docs";
|
||||
import {
|
||||
BaseAppConnectionSchema,
|
||||
GenericCreateAppConnectionFieldsSchema,
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { BaseSqlUsernameAndPasswordConnectionSchema } from "../shared/sql";
|
||||
import { MySqlConnectionMethod } from "./mysql-connection-enums";
|
||||
|
||||
export const MySqlConnectionAccessTokenCredentialsSchema = BaseSqlUsernameAndPasswordConnectionSchema;
|
||||
|
||||
const BaseMySqlConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.MySql) });
|
||||
|
||||
export const MySqlConnectionSchema = BaseMySqlConnectionSchema.extend({
|
||||
method: z.literal(MySqlConnectionMethod.UsernameAndPassword),
|
||||
credentials: MySqlConnectionAccessTokenCredentialsSchema
|
||||
});
|
||||
|
||||
export const SanitizedMySqlConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseMySqlConnectionSchema.extend({
|
||||
method: z.literal(MySqlConnectionMethod.UsernameAndPassword),
|
||||
credentials: MySqlConnectionAccessTokenCredentialsSchema.pick({
|
||||
host: true,
|
||||
database: true,
|
||||
port: true,
|
||||
username: true,
|
||||
sslEnabled: true,
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
export const ValidateMySqlConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z
|
||||
.literal(MySqlConnectionMethod.UsernameAndPassword)
|
||||
.describe(AppConnections.CREATE(AppConnection.MySql).method),
|
||||
credentials: MySqlConnectionAccessTokenCredentialsSchema.describe(
|
||||
AppConnections.CREATE(AppConnection.MySql).credentials
|
||||
)
|
||||
})
|
||||
]);
|
||||
|
||||
export const CreateMySqlConnectionSchema = ValidateMySqlConnectionCredentialsSchema.and(
|
||||
GenericCreateAppConnectionFieldsSchema(AppConnection.MySql, { supportsPlatformManagedCredentials: true })
|
||||
);
|
||||
|
||||
export const UpdateMySqlConnectionSchema = z
|
||||
.object({
|
||||
credentials: MySqlConnectionAccessTokenCredentialsSchema.optional().describe(
|
||||
AppConnections.UPDATE(AppConnection.MySql).credentials
|
||||
)
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.MySql, { supportsPlatformManagedCredentials: true }));
|
||||
|
||||
export const MySqlConnectionListItemSchema = z.object({
|
||||
name: z.literal("MySQL"),
|
||||
app: z.literal(AppConnection.MySql),
|
||||
methods: z.nativeEnum(MySqlConnectionMethod).array(),
|
||||
supportsPlatformManagement: z.literal(true)
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
import z from "zod";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import {
|
||||
CreateMySqlConnectionSchema,
|
||||
MySqlConnectionSchema,
|
||||
ValidateMySqlConnectionCredentialsSchema
|
||||
} from "./mysql-connection-schemas";
|
||||
|
||||
export type TMySqlConnection = z.infer<typeof MySqlConnectionSchema>;
|
||||
|
||||
export type TMySqlConnectionInput = z.infer<typeof CreateMySqlConnectionSchema> & {
|
||||
app: AppConnection.MySql;
|
||||
};
|
||||
|
||||
export type TValidateMySqlConnectionCredentialsSchema = typeof ValidateMySqlConnectionCredentialsSchema;
|
@ -15,7 +15,8 @@ const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
||||
|
||||
const SQL_CONNECTION_CLIENT_MAP = {
|
||||
[AppConnection.Postgres]: "pg",
|
||||
[AppConnection.MsSql]: "mssql"
|
||||
[AppConnection.MsSql]: "mssql",
|
||||
[AppConnection.MySql]: "mysql2"
|
||||
};
|
||||
|
||||
const getConnectionConfig = ({
|
||||
@ -45,6 +46,17 @@ const getConnectionConfig = ({
|
||||
: { encrypt: false }
|
||||
};
|
||||
}
|
||||
case AppConnection.MySql: {
|
||||
return {
|
||||
ssl: sslEnabled
|
||||
? {
|
||||
rejectUnauthorized: sslRejectUnauthorized,
|
||||
ca: sslCertificate,
|
||||
servername: host
|
||||
}
|
||||
: false
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unhandled SQL Connection Config: ${app as AppConnection}`);
|
||||
}
|
||||
@ -101,7 +113,8 @@ export const SQL_CONNECTION_ALTER_LOGIN_STATEMENT: Record<
|
||||
(credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => [string, Knex.RawBinding]
|
||||
> = {
|
||||
[AppConnection.Postgres]: ({ username, password }) => [`ALTER USER ?? WITH PASSWORD '${password}';`, [username]],
|
||||
[AppConnection.MsSql]: ({ username, password }) => [`ALTER LOGIN ?? WITH PASSWORD = '${password}';`, [username]]
|
||||
[AppConnection.MsSql]: ({ username, password }) => [`ALTER LOGIN ?? WITH PASSWORD = '${password}';`, [username]],
|
||||
[AppConnection.MySql]: ({ username, password }) => [`ALTER USER ??@'%' IDENTIFIED BY '${password}';`, [username]]
|
||||
};
|
||||
|
||||
export const transferSqlConnectionCredentialsToPlatform = async (
|
||||
|
@ -16,12 +16,14 @@ const HCVaultSyncDestinationConfigSchema = z.object({
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Secrets Engine Mount required")
|
||||
.max(128)
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.HC_VAULT.mount),
|
||||
path: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Path required")
|
||||
.transform((val) => val.replace(/^\/+|\/+$/g, "")) // removes leading/trailing slashes
|
||||
.max(128)
|
||||
.transform((val) => new RE2("^/+|/+$", "g").replace(val, "")) // removes leading/trailing slashes
|
||||
.refine((val) => new RE2("^([a-zA-Z0-9._-]+/)*[a-zA-Z0-9._-]+$").test(val), {
|
||||
message:
|
||||
"Invalid Vault path format. Use alphanumerics, dots, dashes, underscores, and single slashes between segments."
|
||||
|
@ -12,6 +12,35 @@ import (
|
||||
|
||||
const USER_AGENT = "cli"
|
||||
|
||||
const (
|
||||
operationCallGetRawSecretsV3 = "CallGetRawSecretsV3"
|
||||
operationCallGetEncryptedWorkspaceKey = "CallGetEncryptedWorkspaceKey"
|
||||
operationCallGetServiceTokenDetails = "CallGetServiceTokenDetails"
|
||||
operationCallLogin1V3 = "CallLogin1V3"
|
||||
operationCallVerifyMfaToken = "CallVerifyMfaToken"
|
||||
operationCallLogin2V3 = "CallLogin2V3"
|
||||
operationCallGetAllOrganizations = "CallGetAllOrganizations"
|
||||
operationCallSelectOrganization = "CallSelectOrganization"
|
||||
operationCallGetAllWorkSpacesUserBelongsTo = "CallGetAllWorkSpacesUserBelongsTo"
|
||||
operationCallGetProjectById = "CallGetProjectById"
|
||||
operationCallIsAuthenticated = "CallIsAuthenticated"
|
||||
operationCallGetNewAccessTokenWithRefreshToken = "CallGetNewAccessTokenWithRefreshToken"
|
||||
operationCallGetFoldersV1 = "CallGetFoldersV1"
|
||||
operationCallCreateFolderV1 = "CallCreateFolderV1"
|
||||
operationCallDeleteFolderV1 = "CallDeleteFolderV1"
|
||||
operationCallDeleteSecretsV3 = "CallDeleteSecretsV3"
|
||||
operationCallCreateServiceToken = "CallCreateServiceToken"
|
||||
operationCallUniversalAuthLogin = "CallUniversalAuthLogin"
|
||||
operationCallMachineIdentityRefreshAccessToken = "CallMachineIdentityRefreshAccessToken"
|
||||
operationCallFetchSingleSecretByName = "CallFetchSingleSecretByName"
|
||||
operationCallCreateRawSecretsV3 = "CallCreateRawSecretsV3"
|
||||
operationCallUpdateRawSecretsV3 = "CallUpdateRawSecretsV3"
|
||||
operationCallRegisterGatewayIdentityV1 = "CallRegisterGatewayIdentityV1"
|
||||
operationCallExchangeRelayCertV1 = "CallExchangeRelayCertV1"
|
||||
operationCallGatewayHeartBeatV1 = "CallGatewayHeartBeatV1"
|
||||
operationCallBootstrapInstance = "CallBootstrapInstance"
|
||||
)
|
||||
|
||||
func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncryptedWorkspaceKeyRequest) (GetEncryptedWorkspaceKeyResponse, error) {
|
||||
endpoint := fmt.Sprintf("%v/v2/workspace/%v/encrypted-key", config.INFISICAL_URL, request.WorkspaceId)
|
||||
var result GetEncryptedWorkspaceKeyResponse
|
||||
@ -22,11 +51,11 @@ func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncrypted
|
||||
Get(endpoint)
|
||||
|
||||
if err != nil {
|
||||
return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unable to complete api request [err=%s]", err)
|
||||
return GetEncryptedWorkspaceKeyResponse{}, NewGenericRequestError(operationCallGetEncryptedWorkspaceKey, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unsuccessful response [%v %v] [status-code=%v]", response.Request.Method, response.Request.URL, response.StatusCode())
|
||||
return GetEncryptedWorkspaceKeyResponse{}, NewAPIErrorWithResponse(operationCallGetEncryptedWorkspaceKey, response, nil)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@ -41,11 +70,11 @@ func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDeta
|
||||
Get(fmt.Sprintf("%v/v2/service-token", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unable to complete api request [err=%s]", err)
|
||||
return GetServiceTokenDetailsResponse{}, NewGenericRequestError(operationCallGetServiceTokenDetails, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unsuccessful response: [response=%s]", response)
|
||||
return GetServiceTokenDetailsResponse{}, NewAPIErrorWithResponse(operationCallGetServiceTokenDetails, response, nil)
|
||||
}
|
||||
|
||||
return tokenDetailsResponse, nil
|
||||
@ -61,11 +90,11 @@ func CallLogin1V2(httpClient *resty.Client, request GetLoginOneV2Request) (GetLo
|
||||
Post(fmt.Sprintf("%v/v3/auth/login1", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V3: Unable to complete api request [err=%s]", err)
|
||||
return GetLoginOneV2Response{}, NewGenericRequestError(operationCallLogin1V3, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V3: Unsuccessful response: [response=%s]", response)
|
||||
return GetLoginOneV2Response{}, NewAPIErrorWithResponse(operationCallLogin1V3, response, nil)
|
||||
}
|
||||
|
||||
return loginOneV2Response, nil
|
||||
@ -99,7 +128,7 @@ func CallVerifyMfaToken(httpClient *resty.Client, request VerifyMfaTokenRequest)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("CallVerifyMfaToken: Unable to complete api request [err=%s]", err)
|
||||
return nil, nil, NewGenericRequestError(operationCallVerifyMfaToken, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
@ -135,11 +164,11 @@ func CallLogin2V2(httpClient *resty.Client, request GetLoginTwoV2Request) (GetLo
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V3: Unable to complete api request [err=%s]", err)
|
||||
return GetLoginTwoV2Response{}, NewGenericRequestError(operationCallLogin2V3, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V3: Unsuccessful response: [response=%s]", response)
|
||||
return GetLoginTwoV2Response{}, NewAPIErrorWithResponse(operationCallLogin2V3, response, nil)
|
||||
}
|
||||
|
||||
return loginTwoV2Response, nil
|
||||
@ -154,11 +183,11 @@ func CallGetAllOrganizations(httpClient *resty.Client) (GetOrganizationsResponse
|
||||
Get(fmt.Sprintf("%v/v1/organization", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return GetOrganizationsResponse{}, err
|
||||
return GetOrganizationsResponse{}, NewGenericRequestError(operationCallGetAllOrganizations, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetOrganizationsResponse{}, fmt.Errorf("CallGetAllOrganizations: Unsuccessful response: [response=%v]", response)
|
||||
return GetOrganizationsResponse{}, NewAPIErrorWithResponse(operationCallGetAllOrganizations, response, nil)
|
||||
}
|
||||
|
||||
return orgResponse, nil
|
||||
@ -175,11 +204,11 @@ func CallSelectOrganization(httpClient *resty.Client, request SelectOrganization
|
||||
Post(fmt.Sprintf("%v/v3/auth/select-organization", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return SelectOrganizationResponse{}, err
|
||||
return SelectOrganizationResponse{}, NewGenericRequestError(operationCallSelectOrganization, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return SelectOrganizationResponse{}, fmt.Errorf("CallSelectOrganization: Unsuccessful response: [response=%v]", response)
|
||||
return SelectOrganizationResponse{}, NewAPIErrorWithResponse(operationCallSelectOrganization, response, nil)
|
||||
}
|
||||
|
||||
return selectOrgResponse, nil
|
||||
@ -214,11 +243,11 @@ func CallGetProjectById(httpClient *resty.Client, id string) (Project, error) {
|
||||
Get(fmt.Sprintf("%v/v1/workspace/%s", config.INFISICAL_URL, id))
|
||||
|
||||
if err != nil {
|
||||
return Project{}, err
|
||||
return Project{}, NewGenericRequestError(operationCallGetProjectById, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return Project{}, fmt.Errorf("CallGetProjectById: Unsuccessful response: [response=%v]", response)
|
||||
return Project{}, NewAPIErrorWithResponse(operationCallGetProjectById, response, nil)
|
||||
}
|
||||
|
||||
return projectResponse.Project, nil
|
||||
@ -237,7 +266,7 @@ func CallIsAuthenticated(httpClient *resty.Client) bool {
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
log.Debug().Msgf("CallIsAuthenticated: Unsuccessful response: [response=%v]", response)
|
||||
log.Debug().Msgf("%s: Unsuccessful response: [response=%v]", operationCallIsAuthenticated, response)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -257,11 +286,11 @@ func CallGetNewAccessTokenWithRefreshToken(httpClient *resty.Client, refreshToke
|
||||
Post(fmt.Sprintf("%v/v1/auth/token", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return GetNewAccessTokenWithRefreshTokenResponse{}, err
|
||||
return GetNewAccessTokenWithRefreshTokenResponse{}, NewGenericRequestError(operationCallGetNewAccessTokenWithRefreshToken, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetNewAccessTokenWithRefreshTokenResponse{}, fmt.Errorf("CallGetNewAccessTokenWithRefreshToken: Unsuccessful response: [response=%v]", response)
|
||||
return GetNewAccessTokenWithRefreshTokenResponse{}, NewAPIErrorWithResponse(operationCallGetNewAccessTokenWithRefreshToken, response, nil)
|
||||
}
|
||||
|
||||
return newAccessToken, nil
|
||||
@ -280,11 +309,11 @@ func CallGetFoldersV1(httpClient *resty.Client, request GetFoldersV1Request) (Ge
|
||||
response, err := httpRequest.Get(fmt.Sprintf("%v/v1/folders", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return GetFoldersV1Response{}, fmt.Errorf("CallGetFoldersV1: Unable to complete api request [err=%v]", err)
|
||||
return GetFoldersV1Response{}, NewGenericRequestError(operationCallGetFoldersV1, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetFoldersV1Response{}, fmt.Errorf("CallGetFoldersV1: Unsuccessful [response=%s]", response)
|
||||
return GetFoldersV1Response{}, NewAPIErrorWithResponse(operationCallGetFoldersV1, response, nil)
|
||||
}
|
||||
|
||||
return foldersResponse, nil
|
||||
@ -300,11 +329,11 @@ func CallCreateFolderV1(httpClient *resty.Client, request CreateFolderV1Request)
|
||||
|
||||
response, err := httpRequest.Post(fmt.Sprintf("%v/v1/folders", config.INFISICAL_URL))
|
||||
if err != nil {
|
||||
return CreateFolderV1Response{}, fmt.Errorf("CallCreateFolderV1: Unable to complete api request [err=%s]", err)
|
||||
return CreateFolderV1Response{}, NewGenericRequestError(operationCallCreateFolderV1, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return CreateFolderV1Response{}, fmt.Errorf("CallCreateFolderV1: Unsuccessful [response=%s]", response.String())
|
||||
return CreateFolderV1Response{}, NewAPIErrorWithResponse(operationCallCreateFolderV1, response, nil)
|
||||
}
|
||||
|
||||
return folderResponse, nil
|
||||
@ -321,11 +350,11 @@ func CallDeleteFolderV1(httpClient *resty.Client, request DeleteFolderV1Request)
|
||||
|
||||
response, err := httpRequest.Delete(fmt.Sprintf("%v/v1/folders/%v", config.INFISICAL_URL, request.FolderName))
|
||||
if err != nil {
|
||||
return DeleteFolderV1Response{}, fmt.Errorf("CallDeleteFolderV1: Unable to complete api request [err=%s]", err)
|
||||
return DeleteFolderV1Response{}, NewGenericRequestError(operationCallDeleteFolderV1, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return DeleteFolderV1Response{}, fmt.Errorf("CallDeleteFolderV1: Unsuccessful [response=%s]", response.String())
|
||||
return DeleteFolderV1Response{}, NewAPIErrorWithResponse(operationCallDeleteFolderV1, response, nil)
|
||||
}
|
||||
|
||||
return folderResponse, nil
|
||||
@ -342,11 +371,12 @@ func CallDeleteSecretsRawV3(httpClient *resty.Client, request DeleteSecretV3Requ
|
||||
Delete(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("CallDeleteSecretsV3: Unable to complete api request [err=%s]", err)
|
||||
return NewGenericRequestError(operationCallDeleteSecretsV3, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return fmt.Errorf("CallDeleteSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
|
||||
additionalContext := "Please make sure your secret path, workspace and environment name are all correct."
|
||||
return NewAPIErrorWithResponse(operationCallDeleteSecretsV3, response, &additionalContext)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -362,11 +392,11 @@ func CallCreateServiceToken(httpClient *resty.Client, request CreateServiceToken
|
||||
Post(fmt.Sprintf("%v/v2/service-token/", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return CreateServiceTokenResponse{}, fmt.Errorf("CallCreateServiceToken: Unable to complete api request [err=%s]", err)
|
||||
return CreateServiceTokenResponse{}, NewGenericRequestError(operationCallCreateServiceToken, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return CreateServiceTokenResponse{}, fmt.Errorf("CallCreateServiceToken: Unsuccessful response [%v %v] [status-code=%v]", response.Request.Method, response.Request.URL, response.StatusCode())
|
||||
return CreateServiceTokenResponse{}, NewAPIErrorWithResponse(operationCallCreateServiceToken, response, nil)
|
||||
}
|
||||
|
||||
return createServiceTokenResponse, nil
|
||||
@ -382,11 +412,11 @@ func CallUniversalAuthLogin(httpClient *resty.Client, request UniversalAuthLogin
|
||||
Post(fmt.Sprintf("%v/v1/auth/universal-auth/login/", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return UniversalAuthLoginResponse{}, fmt.Errorf("CallUniversalAuthLogin: Unable to complete api request [err=%s]", err)
|
||||
return UniversalAuthLoginResponse{}, NewGenericRequestError(operationCallUniversalAuthLogin, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return UniversalAuthLoginResponse{}, fmt.Errorf("CallUniversalAuthLogin: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return UniversalAuthLoginResponse{}, NewAPIErrorWithResponse(operationCallUniversalAuthLogin, response, nil)
|
||||
}
|
||||
|
||||
return universalAuthLoginResponse, nil
|
||||
@ -402,11 +432,11 @@ func CallMachineIdentityRefreshAccessToken(httpClient *resty.Client, request Uni
|
||||
Post(fmt.Sprintf("%v/v1/auth/token/renew", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return UniversalAuthRefreshResponse{}, fmt.Errorf("CallMachineIdentityRefreshAccessToken: Unable to complete api request [err=%s]", err)
|
||||
return UniversalAuthRefreshResponse{}, NewGenericRequestError(operationCallMachineIdentityRefreshAccessToken, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return UniversalAuthRefreshResponse{}, fmt.Errorf("CallMachineIdentityRefreshAccessToken: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return UniversalAuthRefreshResponse{}, NewAPIErrorWithResponse(operationCallMachineIdentityRefreshAccessToken, response, nil)
|
||||
}
|
||||
|
||||
return universalAuthRefreshResponse, nil
|
||||
@ -441,19 +471,19 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
|
||||
response, err := req.Get(fmt.Sprintf("%v/v3/secrets/raw", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unable to complete api request [err=%w]", err)
|
||||
return GetRawSecretsV3Response{}, NewGenericRequestError(operationCallGetRawSecretsV3, err)
|
||||
}
|
||||
|
||||
if response.IsError() &&
|
||||
(strings.Contains(response.String(), "bot_not_found_error") ||
|
||||
strings.Contains(strings.ToLower(response.String()), "failed to find bot key") ||
|
||||
strings.Contains(strings.ToLower(response.String()), "bot is not active")) {
|
||||
return GetRawSecretsV3Response{}, fmt.Errorf(`Project with id %s is incompatible with your current CLI version. Upgrade your project by visiting the project settings page. If you're self-hosting and project upgrade option isn't yet available, contact your administrator to upgrade your Infisical instance to the latest release.
|
||||
`, request.WorkspaceId)
|
||||
additionalContext := fmt.Sprintf(`Project with id %s is incompatible with your current CLI version. Upgrade your project by visiting the project settings page. If you're self-hosting and project upgrade option isn't yet available, contact your administrator to upgrade your Infisical instance to the latest release.`, request.WorkspaceId)
|
||||
return GetRawSecretsV3Response{}, NewAPIErrorWithResponse(operationCallGetRawSecretsV3, response, &additionalContext)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return GetRawSecretsV3Response{}, NewAPIErrorWithResponse(operationCallGetRawSecretsV3, response, nil)
|
||||
}
|
||||
|
||||
getRawSecretsV3Response.ETag = response.Header().Get(("etag"))
|
||||
@ -477,11 +507,11 @@ func CallFetchSingleSecretByName(httpClient *resty.Client, request GetRawSecretV
|
||||
Get(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
|
||||
|
||||
if err != nil {
|
||||
return GetRawSecretV3ByNameResponse{}, fmt.Errorf("CallFetchSingleSecretByName: Unable to complete api request [err=%w]", err)
|
||||
return GetRawSecretV3ByNameResponse{}, NewGenericRequestError(operationCallFetchSingleSecretByName, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetRawSecretV3ByNameResponse{}, fmt.Errorf("CallFetchSingleSecretByName: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return GetRawSecretV3ByNameResponse{}, NewAPIErrorWithResponse(operationCallFetchSingleSecretByName, response, nil)
|
||||
}
|
||||
|
||||
getRawSecretV3ByNameResponse.ETag = response.Header().Get(("etag"))
|
||||
@ -517,11 +547,11 @@ func CallCreateRawSecretsV3(httpClient *resty.Client, request CreateRawSecretV3R
|
||||
Post(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("CallCreateRawSecretsV3: Unable to complete api request [err=%w]", err)
|
||||
return NewGenericRequestError(operationCallCreateRawSecretsV3, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return fmt.Errorf("CallCreateRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return NewAPIErrorWithResponse(operationCallCreateRawSecretsV3, response, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -535,11 +565,11 @@ func CallUpdateRawSecretsV3(httpClient *resty.Client, request UpdateRawSecretByN
|
||||
Patch(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("CallUpdateRawSecretsV3: Unable to complete api request [err=%w]", err)
|
||||
return NewGenericRequestError(operationCallUpdateRawSecretsV3, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return fmt.Errorf("CallUpdateRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return NewAPIErrorWithResponse(operationCallUpdateRawSecretsV3, response, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -554,11 +584,11 @@ func CallRegisterGatewayIdentityV1(httpClient *resty.Client) (*GetRelayCredentia
|
||||
Post(fmt.Sprintf("%v/v1/gateways/register-identity", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CallRegisterGatewayIdentityV1: Unable to complete api request [err=%w]", err)
|
||||
return nil, NewGenericRequestError(operationCallRegisterGatewayIdentityV1, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return nil, fmt.Errorf("CallRegisterGatewayIdentityV1: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return nil, NewAPIErrorWithResponse(operationCallRegisterGatewayIdentityV1, response, nil)
|
||||
}
|
||||
|
||||
return &resBody, nil
|
||||
@ -574,11 +604,11 @@ func CallExchangeRelayCertV1(httpClient *resty.Client, request ExchangeRelayCert
|
||||
Post(fmt.Sprintf("%v/v1/gateways/exchange-cert", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CallExchangeRelayCertV1: Unable to complete api request [err=%w]", err)
|
||||
return nil, NewGenericRequestError(operationCallExchangeRelayCertV1, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return nil, fmt.Errorf("CallExchangeRelayCertV1: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return nil, NewAPIErrorWithResponse(operationCallExchangeRelayCertV1, response, nil)
|
||||
}
|
||||
|
||||
return &resBody, nil
|
||||
@ -591,11 +621,11 @@ func CallGatewayHeartBeatV1(httpClient *resty.Client) error {
|
||||
Post(fmt.Sprintf("%v/v1/gateways/heartbeat", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("CallGatewayHeartBeatV1: Unable to complete api request [err=%w]", err)
|
||||
return NewGenericRequestError(operationCallGatewayHeartBeatV1, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return fmt.Errorf("CallGatewayHeartBeatV1: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return NewAPIErrorWithResponse(operationCallGatewayHeartBeatV1, response, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -611,11 +641,11 @@ func CallBootstrapInstance(httpClient *resty.Client, request BootstrapInstanceRe
|
||||
Post(fmt.Sprintf("%v/v1/admin/bootstrap", request.Domain))
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CallBootstrapInstance: Unable to complete api request [err=%w]", err)
|
||||
return nil, NewGenericRequestError(operationCallBootstrapInstance, err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return nil, fmt.Errorf("CallBootstrapInstance: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return nil, NewAPIErrorWithResponse(operationCallBootstrapInstance, response, nil)
|
||||
}
|
||||
|
||||
return resBody, nil
|
||||
|
80
cli/packages/api/errors.go
Normal file
@ -0,0 +1,80 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/infisical/go-sdk/packages/util"
|
||||
)
|
||||
|
||||
type GenericRequestError struct {
|
||||
err error
|
||||
operation string
|
||||
}
|
||||
|
||||
func (e *GenericRequestError) Error() string {
|
||||
return fmt.Sprintf("%s: Unable to complete api request [err=%v]", e.operation, e.err)
|
||||
}
|
||||
|
||||
func NewGenericRequestError(operation string, err error) *GenericRequestError {
|
||||
return &GenericRequestError{err: err, operation: operation}
|
||||
}
|
||||
|
||||
// APIError represents an error response from the API
|
||||
type APIError struct {
|
||||
AdditionalContext string `json:"additionalContext,omitempty"`
|
||||
Operation string `json:"operation"`
|
||||
Method string `json:"method"`
|
||||
URL string `json:"url"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
ErrorMessage string `json:"message,omitempty"`
|
||||
ReqId string `json:"reqId,omitempty"`
|
||||
}
|
||||
|
||||
func (e *APIError) Error() string {
|
||||
msg := fmt.Sprintf(
|
||||
"%s Unsuccessful response [%v %v] [status-code=%v] [request-id=%v]",
|
||||
e.Operation,
|
||||
e.Method,
|
||||
e.URL,
|
||||
e.StatusCode,
|
||||
e.ReqId,
|
||||
)
|
||||
|
||||
if e.ErrorMessage != "" {
|
||||
msg = fmt.Sprintf("%s [message=\"%s\"]", msg, e.ErrorMessage)
|
||||
}
|
||||
|
||||
if e.AdditionalContext != "" {
|
||||
msg = fmt.Sprintf("%s [additional-context=\"%s\"]", msg, e.AdditionalContext)
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func NewAPIErrorWithResponse(operation string, res *resty.Response, additionalContext *string) error {
|
||||
errorMessage := util.TryParseErrorBody(res)
|
||||
reqId := util.TryExtractReqId(res)
|
||||
|
||||
if res == nil {
|
||||
return NewGenericRequestError(operation, fmt.Errorf("response is nil"))
|
||||
}
|
||||
|
||||
apiError := &APIError{
|
||||
Operation: operation,
|
||||
Method: res.Request.Method,
|
||||
URL: res.Request.URL,
|
||||
StatusCode: res.StatusCode(),
|
||||
ReqId: reqId,
|
||||
}
|
||||
|
||||
if additionalContext != nil && *additionalContext != "" {
|
||||
apiError.AdditionalContext = *additionalContext
|
||||
}
|
||||
|
||||
if errorMessage != "" {
|
||||
apiError.ErrorMessage = errorMessage
|
||||
}
|
||||
|
||||
return apiError
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
error: CallGetRawSecretsV3: Unsuccessful response [GET https://app.infisical.com/api/v3/secrets/raw?environment=invalid-env&expandSecretReferences=true&include_imports=true&recursive=true&secretPath=%2F&workspaceId=bef697d4-849b-4a75-b284-0922f87f8ba2] [status-code=404] [response={"error":"NotFound","message":"Environment with slug 'invalid-env' in project with ID bef697d4-849b-4a75-b284-0922f87f8ba2 not found","statusCode":404}]
|
||||
error: CallGetRawSecretsV3 Unsuccessful response [GET https://app.infisical.com/api/v3/secrets/raw?environment=invalid-env&expandSecretReferences=true&include_imports=true&recursive=true&secretPath=%2F&workspaceId=bef697d4-849b-4a75-b284-0922f87f8ba2] [status-code=404] [request-id=<unknown-value>] [message="Environment with slug 'invalid-env' in project with ID bef697d4-849b-4a75-b284-0922f87f8ba2 not found"]
|
||||
|
||||
|
||||
If this issue continues, get support at https://infisical.com/slack
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -71,7 +72,11 @@ func SetupCli() {
|
||||
}
|
||||
|
||||
func FilterRequestID(input string) string {
|
||||
// Find the JSON part of the error message
|
||||
requestIDPattern := regexp.MustCompile(`\[request-id=[^\]]+\]`)
|
||||
reqIDPattern := regexp.MustCompile(`\[reqId=[^\]]+\]`)
|
||||
input = requestIDPattern.ReplaceAllString(input, "[request-id=<unknown-value>]")
|
||||
input = reqIDPattern.ReplaceAllString(input, "[reqId=<unknown-value>]")
|
||||
|
||||
start := strings.Index(input, "{")
|
||||
end := strings.LastIndex(input, "}") + 1
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Available"
|
||||
openapi: "GET /api/v1/app-connections/mysql/available"
|
||||
---
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "Create"
|
||||
openapi: "POST /api/v1/app-connections/mysql"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Check out the configuration docs for [MySQL Connections](/integrations/app-connections/mysql) to learn how to obtain the required credentials.
|
||||
</Note>
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Delete"
|
||||
openapi: "DELETE /api/v1/app-connections/mysql/{connectionId}"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get by ID"
|
||||
openapi: "GET /api/v1/app-connections/mysql/{connectionId}"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get by Name"
|
||||
openapi: "GET /api/v1/app-connections/mysql/connection-name/{connectionName}"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "List"
|
||||
openapi: "GET /api/v1/app-connections/mysql"
|
||||
---
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "Update"
|
||||
openapi: "PATCH /api/v1/app-connections/mysql/{connectionId}"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Check out the configuration docs for [MySQL Connections](/integrations/app-connections/mysql) to learn how to obtain the required credentials.
|
||||
</Note>
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "Create"
|
||||
openapi: "POST /api/v2/secret-rotations/mysql-credentials"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Check out the configuration docs for [MySQL Credentials Rotations](/documentation/platform/secret-rotation/mysql-credentials) to learn how to obtain the required parameters.
|
||||
</Note>
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Delete"
|
||||
openapi: "DELETE /api/v2/secret-rotations/mysql-credentials/{rotationId}"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get by ID"
|
||||
openapi: "GET /api/v2/secret-rotations/mysql-credentials/{rotationId}"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get by Name"
|
||||
openapi: "GET /api/v2/secret-rotations/mysql-credentials/rotation-name/{rotationName}"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get Credentials by ID"
|
||||
openapi: "GET /api/v2/secret-rotations/mysql-credentials/{rotationId}/generated-credentials"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "List"
|
||||
openapi: "GET /api/v2/secret-rotations/mysql-credentials"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Rotate Secrets"
|
||||
openapi: "POST /api/v2/secret-rotations/mysql-credentials/{rotationId}/rotate-secrets"
|
||||
---
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "Update"
|
||||
openapi: "PATCH /api/v2/secret-rotations/mysql-credentials/{rotationId}"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Check out the configuration docs for [MySQL Credentials Rotations](/documentation/platform/secret-rotation/mysql-credentials) to learn how to obtain the required parameters.
|
||||
</Note>
|
@ -29,7 +29,6 @@ Using a hardware security module comes with the added benefit of having a secure
|
||||
|
||||
Enabling HSM encryption has a set of key benefits:
|
||||
1. **Root Key Wrapping**: The root KMS encryption key that is used to secure your Infisical instance will be encrypted using the HSM device rather than the standard software-protected key.
|
||||
2. **FIPS 140-2/3 Compliance**: Using an HSM device ensures that your Infisical instance is FIPS 140-2 or FIPS 140-3 compliant. For FIPS 140-3, ensure that your HSM is FIPS 140-3 validated.
|
||||
|
||||
#### Caveats
|
||||
- **Performance**: Using an HSM device can have a performance impact on your Infisical instance. This is due to the additional latency introduced by the HSM device. This is however only noticeable when your instance(s) start up or when the encryption strategy is changed.
|
||||
@ -41,13 +40,6 @@ Enabling HSM encryption has a set of key benefits:
|
||||
- An HSM device from a provider such as [Thales Luna HSM](https://cpl.thalesgroup.com/encryption/data-protection-on-demand/services/luna-cloud-hsm), [AWS CloudHSM](https://aws.amazon.com/cloudhsm/), [Fortanix HSM](https://www.fortanix.com/platform/data-security-manager), or others.
|
||||
|
||||
|
||||
### FIPS Compliance
|
||||
FIPS, also known as the Federal Information Processing Standard, is a set of standards that are used to accredit cryptographic modules. FIPS 140-2 and FIPS 140-3 are the two most common standards used for cryptographic modules. If your HSM uses FIPS 140-3 validated hardware, Infisical will automatically be FIPS 140-3 compliant. If your HSM uses FIPS 140-2 validated hardware, Infisical will be FIPS 140-2 compliant.
|
||||
|
||||
HSM devices are especially useful for organizations that operate in regulated industries such as healthcare, finance, and government, where data security and compliance are of the utmost importance.
|
||||
|
||||
For organizations that work with US government agencies, FIPS compliance is almost always a requirement when dealing with sensitive information. FIPS compliance ensures that the cryptographic modules used by the organization meet the security requirements set by the US government.
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
|
||||
|
@ -0,0 +1,158 @@
|
||||
---
|
||||
title: "MySQL Credentials Rotation"
|
||||
description: "Learn how to automatically rotate MySQL credentials."
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Create a [MySQL Connection](/integrations/app-connections/mysql) with the required **Secret Rotation** permissions
|
||||
2. Create two designated database users for Infisical to rotate the credentials for. Be sure to grant each user login permissions for the desired database with the necessary privileges their use case will require.
|
||||
|
||||
An example creation statement might look like:
|
||||
```SQL
|
||||
-- create user roles
|
||||
CREATE USER 'infisical_user_1'@'%' IDENTIFIED BY 'temporary_password';
|
||||
CREATE USER 'infisical_user_2'@'%' IDENTIFIED BY 'temporary_password';
|
||||
|
||||
-- grant all privileges
|
||||
GRANT ALL PRIVILEGES ON my_database.* TO 'infisical_user_1'@'%';
|
||||
GRANT ALL PRIVILEGES ON my_database.* TO 'infisical_user_2'@'%';
|
||||
|
||||
-- apply the privilege changes
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
<Tip>
|
||||
To learn more about the MySQL permission system, please visit their [documentation](https://dev.mysql.com/doc/refman/8.4/en/grant.html).
|
||||
</Tip>
|
||||
|
||||
|
||||
## Create a MySQL Credentials Rotation in Infisical
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to your Secret Manager Project's Dashboard and select **Add Secret Rotation** from the actions dropdown.
|
||||

|
||||
|
||||
2. Select the **MySQL Credentials** option.
|
||||

|
||||
|
||||
3. Select the **MySQL Connection** to use and configure the rotation behavior. Then click **Next**.
|
||||

|
||||
|
||||
- **MySQL Connection** - the connection that will perform the rotation of the configured database user credentials.
|
||||
- **Rotation Interval** - the interval, in days, that once elapsed will trigger a rotation.
|
||||
- **Rotate At** - the local time of day when rotation should occur once the interval has elapsed.
|
||||
- **Auto-Rotation Enabled** - whether secrets should automatically be rotated once the rotation interval has elapsed. Disable this option to manually rotate secrets or pause secret rotation.
|
||||
|
||||
4. Input the usernames of the database users created above that will be used for rotation. Then click **Next**.
|
||||

|
||||
|
||||
- **Database Username 1** - the username of the first user that will be used for rotation.
|
||||
- **Database Username 2** - the username of the second user that will be used for rotation.
|
||||
|
||||
5. Specify the secret names that the active credentials should be mapped to. Then click **Next**.
|
||||

|
||||
|
||||
- **Username** - the name of the secret that the active username will be mapped to.
|
||||
- **Password** - the name of the secret that the active password will be mapped to.
|
||||
|
||||
6. Give your rotation a name and description (optional). Then click **Next**.
|
||||

|
||||
|
||||
- **Name** - the name of the secret rotation configuration. Must be slug-friendly.
|
||||
- **Description** (optional) - a description of this rotation configuration.
|
||||
|
||||
7. Review your configuration, then click **Create Secret Rotation**.
|
||||

|
||||
|
||||
8. Your **MySQL Credentials** are now available for use via the mapped secrets.
|
||||

|
||||
</Tab>
|
||||
<Tab title="API">
|
||||
To create a MySQL Credentials Rotation, make an API request to the [Create MySQL Credentials Rotation](/api-reference/endpoints/secret-rotations/mysql-credentials/create) API endpoint.
|
||||
|
||||
### Sample request
|
||||
|
||||
```bash Request
|
||||
curl --request POST \
|
||||
--url https://us.infisical.com/api/v2/secret-rotations/mysql-credentials \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"name": "my-mysql-rotation",
|
||||
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"description": "my database credentials rotation",
|
||||
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"environment": "dev",
|
||||
"secretPath": "/",
|
||||
"isAutoRotationEnabled": true,
|
||||
"rotationInterval": 30,
|
||||
"rotateAtUtc": {
|
||||
"hours": 0,
|
||||
"minutes": 0
|
||||
},
|
||||
"parameters": {
|
||||
"username1": "infisical_user_1",
|
||||
"username2": "infisical_user_2"
|
||||
},
|
||||
"secretsMapping": {
|
||||
"username": "MYSQL_USERNAME",
|
||||
"password": "MYSQL_PASSWORD"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Sample response
|
||||
|
||||
```bash Response
|
||||
{
|
||||
"secretRotation": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-mysql-rotation",
|
||||
"description": "my database credentials rotation",
|
||||
"secretsMapping": {
|
||||
"username": "MYSQL_USERNAME",
|
||||
"password": "MYSQL_PASSWORD"
|
||||
},
|
||||
"isAutoRotationEnabled": true,
|
||||
"activeIndex": 0,
|
||||
"folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
"updatedAt": "2023-11-07T05:31:56Z",
|
||||
"rotationInterval": 30,
|
||||
"rotationStatus": "success",
|
||||
"lastRotationAttemptedAt": "2023-11-07T05:31:56Z",
|
||||
"lastRotatedAt": "2023-11-07T05:31:56Z",
|
||||
"lastRotationJobId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"nextRotationAt": "2023-11-07T05:31:56Z",
|
||||
"connection": {
|
||||
"app": "mysql",
|
||||
"name": "my-mysql-connection",
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||
},
|
||||
"environment": {
|
||||
"slug": "dev",
|
||||
"name": "Development",
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||
},
|
||||
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"folder": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"path": "/"
|
||||
},
|
||||
"rotateAtUtc": {
|
||||
"hours": 0,
|
||||
"minutes": 0
|
||||
},
|
||||
"lastRotationMessage": null,
|
||||
"type": "mysql-credentials",
|
||||
"parameters": {
|
||||
"username1": "infisical_user_1",
|
||||
"username2": "infisical_user_2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
After Width: | Height: | Size: 763 KiB |
BIN
docs/images/app-connections/mysql/select-mysql-connection.png
Normal file
After Width: | Height: | Size: 841 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 725 KiB |
After Width: | Height: | Size: 721 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 684 KiB |
After Width: | Height: | Size: 780 KiB |
After Width: | Height: | Size: 693 KiB |
After Width: | Height: | Size: 708 KiB |
129
docs/integrations/app-connections/mysql.mdx
Normal file
@ -0,0 +1,129 @@
|
||||
---
|
||||
title: "MySQL Connection"
|
||||
description: "Learn how to configure a MySQL Connection for Infisical."
|
||||
---
|
||||
|
||||
Infisical supports connecting to MySQL using a database role.
|
||||
|
||||
## Configure a MySQL Role for Infisical
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a Role">
|
||||
Infisical recommends creating a designated role in your MySQL database for your connection.
|
||||
```SQL
|
||||
-- create user role
|
||||
CREATE USER 'infisical_role'@'%' IDENTIFIED BY 'my-password';
|
||||
```
|
||||
</Step>
|
||||
<Step title="Grant Relevant Permissions">
|
||||
Depending on how you intend to use your MySQL connection, you'll need to grant one or more of the following permissions.
|
||||
<Tip>
|
||||
To learn more about MySQL's permission system, please visit their [documentation](https://dev.mysql.com/doc/refman/8.4/en/grant.html).
|
||||
</Tip>
|
||||
<Tabs>
|
||||
<Tab title="Secret Rotation">
|
||||
For Secret Rotations, your Infisical user will require the ability to alter other users' passwords:
|
||||
```SQL
|
||||
-- enable permissions to alter login credentials
|
||||
GRANT CREATE USER ON *.* TO 'infisical_role'@'%';
|
||||
|
||||
-- Apply changes
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
<Step title="Get Connection Details">
|
||||
You'll need the following information to create your MySQL connection:
|
||||
- `host` - The hostname or IP address of your MySQL server
|
||||
- `port` - The port number your MySQL server is listening on (default: 3306)
|
||||
- `database` - The name of the specific database you want to connect to
|
||||
- `username` - The role name of the login created in the steps above
|
||||
- `password` - The role password of the login created in the steps above
|
||||
- `sslCertificate` (optional) - The SSL certificate required for connection (if configured)
|
||||
|
||||
<Note>
|
||||
If you are self-hosting Infisical and intend to connect to an internal/private IP address, be sure to set the `ALLOW_INTERNAL_IP_CONNECTIONS` environment variable to `true`.
|
||||
</Note>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Create Connection in Infisical
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
||||

|
||||
|
||||
2. Select the **MySQL Connection** option.
|
||||

|
||||
|
||||
3. Select the **Username & Password** method option and provide the details obtained from the previous section and press **Connect to MySQL**.
|
||||
|
||||
<Note>
|
||||
Optionally, if you'd like Infisical to manage the credentials of this connection, you can enable the Platform Managed Credentials option.
|
||||
If enabled, Infisical will update the password of the connection on creation to prevent external access to this database role.
|
||||
</Note>
|
||||
|
||||

|
||||
|
||||
4. Your **MySQL Connection** is now available for use.
|
||||

|
||||
</Tab>
|
||||
<Tab title="API">
|
||||
To create a MySQL Connection, make an API request to the [Create MySQL Connection](/api-reference/endpoints/app-connections/mysql/create) API endpoint.
|
||||
|
||||
<Note>
|
||||
Optionally, if you'd like Infisical to manage the credentials of this connection, you can set the `isPlatformManagedCredentials` option to `true`.
|
||||
If enabled, Infisical will update the password of the connection on creation to prevent external access to this database role.
|
||||
</Note>
|
||||
|
||||
### Sample request
|
||||
|
||||
```bash Request
|
||||
curl --request POST \
|
||||
--url https://app.infisical.com/api/v1/app-connections/mysql \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"name": "my-mysql-connection",
|
||||
"method": "username-and-password",
|
||||
"isPlatformManagedCredentials": true,
|
||||
"credentials": {
|
||||
"host": "123.4.5.6",
|
||||
"port": 3306,
|
||||
"database": "default",
|
||||
"username": "infisical_role",
|
||||
"password": "my-password",
|
||||
"sslEnabled": true,
|
||||
"sslRejectUnauthorized": true
|
||||
},
|
||||
}'
|
||||
```
|
||||
|
||||
### Sample response
|
||||
|
||||
```bash Response
|
||||
{
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-mysql-connection",
|
||||
"version": 1,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
"updatedAt": "2023-11-07T05:31:56Z",
|
||||
"app": "mysql",
|
||||
"method": "username-and-password",
|
||||
"isPlatformManagedCredentials": true,
|
||||
"credentials": {
|
||||
"host": "123.4.5.6",
|
||||
"port": 3306,
|
||||
"database": "default",
|
||||
"username": "infisical_role",
|
||||
"sslEnabled": true,
|
||||
"sslRejectUnauthorized": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
@ -199,6 +199,7 @@
|
||||
"documentation/platform/secret-rotation/azure-client-secret",
|
||||
"documentation/platform/secret-rotation/ldap-password",
|
||||
"documentation/platform/secret-rotation/mssql-credentials",
|
||||
"documentation/platform/secret-rotation/mysql-credentials",
|
||||
"documentation/platform/secret-rotation/postgres-credentials"
|
||||
]
|
||||
},
|
||||
@ -493,6 +494,7 @@
|
||||
"integrations/app-connections/humanitec",
|
||||
"integrations/app-connections/ldap",
|
||||
"integrations/app-connections/mssql",
|
||||
"integrations/app-connections/mysql",
|
||||
"integrations/app-connections/oci",
|
||||
"integrations/app-connections/postgres",
|
||||
"integrations/app-connections/teamcity",
|
||||
@ -1005,6 +1007,19 @@
|
||||
"api-reference/endpoints/secret-rotations/mssql-credentials/update"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "MySQL Credentials",
|
||||
"pages": [
|
||||
"api-reference/endpoints/secret-rotations/mysql-credentials/create",
|
||||
"api-reference/endpoints/secret-rotations/mysql-credentials/delete",
|
||||
"api-reference/endpoints/secret-rotations/mysql-credentials/get-by-id",
|
||||
"api-reference/endpoints/secret-rotations/mysql-credentials/get-by-name",
|
||||
"api-reference/endpoints/secret-rotations/mysql-credentials/get-generated-credentials-by-id",
|
||||
"api-reference/endpoints/secret-rotations/mysql-credentials/list",
|
||||
"api-reference/endpoints/secret-rotations/mysql-credentials/rotate-secrets",
|
||||
"api-reference/endpoints/secret-rotations/mysql-credentials/update"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "PostgreSQL Credentials",
|
||||
"pages": [
|
||||
@ -1220,6 +1235,18 @@
|
||||
"api-reference/endpoints/app-connections/mssql/delete"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "MySQL",
|
||||
"pages": [
|
||||
"api-reference/endpoints/app-connections/mysql/list",
|
||||
"api-reference/endpoints/app-connections/mysql/available",
|
||||
"api-reference/endpoints/app-connections/mysql/get-by-id",
|
||||
"api-reference/endpoints/app-connections/mysql/get-by-name",
|
||||
"api-reference/endpoints/app-connections/mysql/create",
|
||||
"api-reference/endpoints/app-connections/mysql/update",
|
||||
"api-reference/endpoints/app-connections/mysql/delete"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "OCI",
|
||||
"pages": [
|
||||
|
@ -62,6 +62,7 @@ const Content = ({ secretRotation }: ContentProps) => {
|
||||
let Component: ReactNode;
|
||||
switch (generatedCredentialsResponse.type) {
|
||||
case SecretRotation.PostgresCredentials:
|
||||
case SecretRotation.MySqlCredentials:
|
||||
case SecretRotation.MsSqlCredentials:
|
||||
Component = (
|
||||
<ViewSqlCredentialsRotationGeneratedCredentials
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { CredentialDisplay } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/shared/CredentialDisplay";
|
||||
import { ViewRotationGeneratedCredentialsDisplay } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/shared/ViewRotationGeneratedCredentialsDisplay";
|
||||
import { TMsSqlCredentialsRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/mssql-credentials-rotation";
|
||||
import { TMySqlCredentialsRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/mysql-credentials-rotation";
|
||||
import { TPostgresCredentialsRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/postgres-credentials-rotation";
|
||||
|
||||
type Props = {
|
||||
generatedCredentialsResponse:
|
||||
| TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
||||
| TMySqlCredentialsRotationGeneratedCredentialsResponse
|
||||
| TPostgresCredentialsRotationGeneratedCredentialsResponse;
|
||||
};
|
||||
|
||||
|
@ -12,6 +12,7 @@ import { SqlCredentialsRotationParametersFields } from "./shared";
|
||||
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
||||
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationParametersFields,
|
||||
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationParametersFields,
|
||||
[SecretRotation.MySqlCredentials]: SqlCredentialsRotationParametersFields,
|
||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationParametersFields,
|
||||
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationParametersFields,
|
||||
[SecretRotation.LdapPassword]: LdapPasswordRotationParametersFields,
|
||||
|
@ -15,6 +15,7 @@ import { SqlCredentialsRotationReviewFields } from "./shared";
|
||||
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
||||
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationReviewFields,
|
||||
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationReviewFields,
|
||||
[SecretRotation.MySqlCredentials]: SqlCredentialsRotationReviewFields,
|
||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationReviewFields,
|
||||
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationReviewFields,
|
||||
[SecretRotation.LdapPassword]: LdapPasswordRotationReviewFields,
|
||||
|
@ -12,6 +12,7 @@ import { SqlCredentialsRotationSecretsMappingFields } from "./shared";
|
||||
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
||||
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationSecretsMappingFields,
|
||||
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationSecretsMappingFields,
|
||||
[SecretRotation.MySqlCredentials]: SqlCredentialsRotationSecretsMappingFields,
|
||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationSecretsMappingFields,
|
||||
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationSecretsMappingFields,
|
||||
[SecretRotation.LdapPassword]: LdapPasswordRotationSecretsMappingFields,
|
||||
|
@ -5,6 +5,7 @@ import { AwsIamUserSecretRotationSchema } from "@app/components/secret-rotations
|
||||
import { AzureClientSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/azure-client-secret-rotation-schema";
|
||||
import { LdapPasswordRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/ldap-password-rotation-schema";
|
||||
import { MsSqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mssql-credentials-rotation-schema";
|
||||
import { MySqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mysql-credentials-rotation-schema";
|
||||
import { PostgresCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/postgres-credentials-rotation-schema";
|
||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||
import { LdapPasswordRotationMethod } from "@app/hooks/api/secretRotationsV2/types/ldap-password-rotation";
|
||||
@ -17,6 +18,7 @@ export const SecretRotationV2FormSchema = (isUpdate: boolean) =>
|
||||
AzureClientSecretRotationSchema,
|
||||
PostgresCredentialsRotationSchema,
|
||||
MsSqlCredentialsRotationSchema,
|
||||
MySqlCredentialsRotationSchema,
|
||||
LdapPasswordRotationSchema,
|
||||
AwsIamUserSecretRotationSchema
|
||||
]),
|
||||
|
@ -0,0 +1,12 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { BaseSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/base-secret-rotation-v2-schema";
|
||||
import { SqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/shared";
|
||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||
|
||||
export const MySqlCredentialsRotationSchema = z
|
||||
.object({
|
||||
type: z.literal(SecretRotation.MySqlCredentials)
|
||||
})
|
||||
.merge(SqlCredentialsRotationSchema)
|
||||
.merge(BaseSecretRotationSchema);
|
@ -23,6 +23,7 @@ import {
|
||||
HumanitecConnectionMethod,
|
||||
LdapConnectionMethod,
|
||||
MsSqlConnectionMethod,
|
||||
MySqlConnectionMethod,
|
||||
OnePassConnectionMethod,
|
||||
PostgresConnectionMethod,
|
||||
TAppConnection,
|
||||
@ -58,6 +59,7 @@ export const APP_CONNECTION_MAP: Record<
|
||||
[AppConnection.Vercel]: { name: "Vercel", image: "Vercel.png" },
|
||||
[AppConnection.Postgres]: { name: "PostgreSQL", image: "Postgres.png" },
|
||||
[AppConnection.MsSql]: { name: "Microsoft SQL Server", image: "MsSql.png" },
|
||||
[AppConnection.MySql]: { name: "MySQL", image: "MySql.png" },
|
||||
[AppConnection.Camunda]: { name: "Camunda", image: "Camunda.png" },
|
||||
[AppConnection.Windmill]: { name: "Windmill", image: "Windmill.png" },
|
||||
[AppConnection.Auth0]: { name: "Auth0", image: "Auth0.png", size: 40 },
|
||||
@ -95,6 +97,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
||||
return { name: "API Token", icon: faKey };
|
||||
case PostgresConnectionMethod.UsernameAndPassword:
|
||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||
case MySqlConnectionMethod.UsernameAndPassword:
|
||||
return { name: "Username & Password", icon: faLock };
|
||||
case HCVaultConnectionMethod.AccessToken:
|
||||
case TeamCityConnectionMethod.AccessToken:
|
||||
|
@ -15,6 +15,11 @@ export const SECRET_ROTATION_MAP: Record<
|
||||
image: "MsSql.png",
|
||||
size: 50
|
||||
},
|
||||
[SecretRotation.MySqlCredentials]: {
|
||||
name: "MySQL Credentials",
|
||||
image: "MySql.png",
|
||||
size: 50
|
||||
},
|
||||
[SecretRotation.Auth0ClientSecret]: {
|
||||
name: "Auth0 Client Secret",
|
||||
image: "Auth0.png",
|
||||
@ -40,6 +45,7 @@ export const SECRET_ROTATION_MAP: Record<
|
||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
||||
[SecretRotation.MySqlCredentials]: AppConnection.MySql,
|
||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
||||
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
||||
[SecretRotation.LdapPassword]: AppConnection.LDAP,
|
||||
@ -50,6 +56,7 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnectio
|
||||
export const IS_ROTATION_DUAL_CREDENTIALS: Record<SecretRotation, boolean> = {
|
||||
[SecretRotation.PostgresCredentials]: true,
|
||||
[SecretRotation.MsSqlCredentials]: true,
|
||||
[SecretRotation.MySqlCredentials]: true,
|
||||
[SecretRotation.Auth0ClientSecret]: false,
|
||||
[SecretRotation.AzureClientSecret]: true,
|
||||
[SecretRotation.LdapPassword]: false,
|
||||
|
@ -11,6 +11,7 @@ export enum AppConnection {
|
||||
Vercel = "vercel",
|
||||
Postgres = "postgres",
|
||||
MsSql = "mssql",
|
||||
MySql = "mysql",
|
||||
Camunda = "camunda",
|
||||
Windmill = "windmill",
|
||||
Auth0 = "auth0",
|
||||
|
@ -60,6 +60,10 @@ export type TMsSqlConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.MsSql;
|
||||
};
|
||||
|
||||
export type TMySqlConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.MySql;
|
||||
};
|
||||
|
||||
export type TCamundaConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.Camunda;
|
||||
};
|
||||
@ -105,6 +109,7 @@ export type TAppConnectionOption =
|
||||
| TVercelConnectionOption
|
||||
| TPostgresConnectionOption
|
||||
| TMsSqlConnectionOption
|
||||
| TMySqlConnectionOption
|
||||
| TCamundaConnectionOption
|
||||
| TWindmillConnectionOption
|
||||
| TAuth0ConnectionOption
|
||||
@ -126,6 +131,7 @@ export type TAppConnectionOptionMap = {
|
||||
[AppConnection.Vercel]: TVercelConnectionOption;
|
||||
[AppConnection.Postgres]: TPostgresConnectionOption;
|
||||
[AppConnection.MsSql]: TMsSqlConnectionOption;
|
||||
[AppConnection.MySql]: TMySqlConnectionOption;
|
||||
[AppConnection.Camunda]: TCamundaConnectionOption;
|
||||
[AppConnection.Windmill]: TWindmillConnectionOption;
|
||||
[AppConnection.Auth0]: TAuth0ConnectionOption;
|
||||
|
@ -14,6 +14,7 @@ import { THCVaultConnection } from "./hc-vault-connection";
|
||||
import { THumanitecConnection } from "./humanitec-connection";
|
||||
import { TLdapConnection } from "./ldap-connection";
|
||||
import { TMsSqlConnection } from "./mssql-connection";
|
||||
import { TMySqlConnection } from "./mysql-connection";
|
||||
import { TOCIConnection } from "./oci-connection";
|
||||
import { TPostgresConnection } from "./postgres-connection";
|
||||
import { TTeamCityConnection } from "./teamcity-connection";
|
||||
@ -35,6 +36,7 @@ export * from "./hc-vault-connection";
|
||||
export * from "./humanitec-connection";
|
||||
export * from "./ldap-connection";
|
||||
export * from "./mssql-connection";
|
||||
export * from "./mysql-connection";
|
||||
export * from "./oci-connection";
|
||||
export * from "./postgres-connection";
|
||||
export * from "./teamcity-connection";
|
||||
@ -55,6 +57,7 @@ export type TAppConnection =
|
||||
| TVercelConnection
|
||||
| TPostgresConnection
|
||||
| TMsSqlConnection
|
||||
| TMySqlConnection
|
||||
| TCamundaConnection
|
||||
| TWindmillConnection
|
||||
| TAuth0Connection
|
||||
@ -102,6 +105,7 @@ export type TAppConnectionMap = {
|
||||
[AppConnection.Vercel]: TVercelConnection;
|
||||
[AppConnection.Postgres]: TPostgresConnection;
|
||||
[AppConnection.MsSql]: TMsSqlConnection;
|
||||
[AppConnection.MySql]: TMySqlConnection;
|
||||
[AppConnection.Camunda]: TCamundaConnection;
|
||||
[AppConnection.Windmill]: TWindmillConnection;
|
||||
[AppConnection.Auth0]: TAuth0Connection;
|
||||
|
@ -0,0 +1,13 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
||||
|
||||
import { TBaseSqlConnectionCredentials } from "./shared";
|
||||
|
||||
export enum MySqlConnectionMethod {
|
||||
UsernameAndPassword = "username-and-password"
|
||||
}
|
||||
|
||||
export type TMySqlConnection = TRootAppConnection & { app: AppConnection.MySql } & {
|
||||
method: MySqlConnectionMethod.UsernameAndPassword;
|
||||
credentials: TBaseSqlConnectionCredentials;
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
export enum SecretRotation {
|
||||
PostgresCredentials = "postgres-credentials",
|
||||
MsSqlCredentials = "mssql-credentials",
|
||||
MySqlCredentials = "mysql-credentials",
|
||||
Auth0ClientSecret = "auth0-client-secret",
|
||||
AzureClientSecret = "azure-client-secret",
|
||||
LdapPassword = "ldap-password",
|
||||
|
@ -31,9 +31,15 @@ import { TSqlCredentialsRotationOption } from "@app/hooks/api/secretRotationsV2/
|
||||
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
||||
import { DiscriminativePick } from "@app/types";
|
||||
|
||||
import {
|
||||
TMySqlCredentialsRotation,
|
||||
TMySqlCredentialsRotationGeneratedCredentialsResponse
|
||||
} from "./mysql-credentials-rotation";
|
||||
|
||||
export type TSecretRotationV2 = (
|
||||
| TPostgresCredentialsRotation
|
||||
| TMsSqlCredentialsRotation
|
||||
| TMySqlCredentialsRotation
|
||||
| TAuth0ClientSecretRotation
|
||||
| TAzureClientSecretRotation
|
||||
| TLdapPasswordRotation
|
||||
@ -56,6 +62,7 @@ export type TSecretRotationV2Response = { secretRotation: TSecretRotationV2 };
|
||||
export type TViewSecretRotationGeneratedCredentialsResponse =
|
||||
| TPostgresCredentialsRotationGeneratedCredentialsResponse
|
||||
| TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
||||
| TMySqlCredentialsRotationGeneratedCredentialsResponse
|
||||
| TAuth0ClientSecretRotationGeneratedCredentialsResponse
|
||||
| TAzureClientSecretRotationGeneratedCredentialsResponse
|
||||
| TLdapPasswordRotationGeneratedCredentialsResponse
|
||||
@ -105,6 +112,7 @@ export type TViewSecretRotationV2GeneratedCredentialsDTO = {
|
||||
export type TSecretRotationOptionMap = {
|
||||
[SecretRotation.PostgresCredentials]: TSqlCredentialsRotationOption;
|
||||
[SecretRotation.MsSqlCredentials]: TSqlCredentialsRotationOption;
|
||||
[SecretRotation.MySqlCredentials]: TSqlCredentialsRotationOption;
|
||||
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationOption;
|
||||
[SecretRotation.AzureClientSecret]: TAzureClientSecretRotationOption;
|
||||
[SecretRotation.LdapPassword]: TLdapPasswordRotationOption;
|
||||
@ -114,6 +122,7 @@ export type TSecretRotationOptionMap = {
|
||||
export type TSecretRotationGeneratedCredentialsResponseMap = {
|
||||
[SecretRotation.PostgresCredentials]: TPostgresCredentialsRotationGeneratedCredentialsResponse;
|
||||
[SecretRotation.MsSqlCredentials]: TMsSqlCredentialsRotationGeneratedCredentialsResponse;
|
||||
[SecretRotation.MySqlCredentials]: TMySqlCredentialsRotationGeneratedCredentialsResponse;
|
||||
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationGeneratedCredentialsResponse;
|
||||
[SecretRotation.AzureClientSecret]: TAzureClientSecretRotationGeneratedCredentialsResponse;
|
||||
[SecretRotation.LdapPassword]: TLdapPasswordRotationGeneratedCredentialsResponse;
|
||||
|
@ -0,0 +1,17 @@
|
||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||
import {
|
||||
TSecretRotationV2Base,
|
||||
TSecretRotationV2GeneratedCredentialsResponseBase,
|
||||
TSqlCredentialsRotationGeneratedCredentials,
|
||||
TSqlCredentialsRotationProperties
|
||||
} from "@app/hooks/api/secretRotationsV2/types/shared";
|
||||
|
||||
export type TMySqlCredentialsRotation = TSecretRotationV2Base & {
|
||||
type: SecretRotation.MySqlCredentials;
|
||||
} & TSqlCredentialsRotationProperties;
|
||||
|
||||
export type TMySqlCredentialsRotationGeneratedCredentialsResponse =
|
||||
TSecretRotationV2GeneratedCredentialsResponseBase<
|
||||
SecretRotation.MySqlCredentials,
|
||||
TSqlCredentialsRotationGeneratedCredentials
|
||||
>;
|
@ -14,8 +14,11 @@ export type TSqlCredentialsRotationProperties = {
|
||||
|
||||
export type TSqlCredentialsRotationOption = {
|
||||
name: string;
|
||||
type: SecretRotation.PostgresCredentials | SecretRotation.MsSqlCredentials;
|
||||
connection: AppConnection.Postgres | AppConnection.MsSql;
|
||||
type:
|
||||
| SecretRotation.PostgresCredentials
|
||||
| SecretRotation.MsSqlCredentials
|
||||
| SecretRotation.MySqlCredentials;
|
||||
connection: AppConnection.Postgres | AppConnection.MsSql | AppConnection.MySql;
|
||||
template: {
|
||||
secretsMapping: TSqlCredentialsRotationProperties["secretsMapping"];
|
||||
createUserStatement: string;
|
||||
|
@ -23,6 +23,7 @@ import { HCVaultConnectionForm } from "./HCVaultConnectionForm";
|
||||
import { HumanitecConnectionForm } from "./HumanitecConnectionForm";
|
||||
import { LdapConnectionForm } from "./LdapConnectionForm";
|
||||
import { MsSqlConnectionForm } from "./MsSqlConnectionForm";
|
||||
import { MySqlConnectionForm } from "./MySqlConnectionForm";
|
||||
import { OCIConnectionForm } from "./OCIConnectionForm";
|
||||
import { PostgresConnectionForm } from "./PostgresConnectionForm";
|
||||
import { TeamCityConnectionForm } from "./TeamCityConnectionForm";
|
||||
@ -89,6 +90,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
return <PostgresConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.MsSql:
|
||||
return <MsSqlConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.MySql:
|
||||
return <MySqlConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Camunda:
|
||||
return <CamundaConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureClientSecrets:
|
||||
@ -165,6 +168,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
return <PostgresConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.MsSql:
|
||||
return <MsSqlConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.MySql:
|
||||
return <MySqlConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Camunda:
|
||||
return <CamundaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.AzureClientSecrets:
|
||||
|
@ -0,0 +1,155 @@
|
||||
import { useState } from "react";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import { MySqlConnectionMethod, TMySqlConnection } from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { PlatformManagedConfirmationModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/shared/PlatformManagedConfirmationModal";
|
||||
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
import {
|
||||
BaseSqlUsernameAndPasswordConnectionSchema,
|
||||
PlatformManagedNoticeBanner,
|
||||
SqlConnectionFields
|
||||
} from "./shared";
|
||||
|
||||
type Props = {
|
||||
appConnection?: TMySqlConnection;
|
||||
onSubmit: (formData: FormData) => Promise<void>;
|
||||
};
|
||||
|
||||
const rootSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.MySql),
|
||||
isPlatformManagedCredentials: z.boolean().optional()
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
rootSchema.extend({
|
||||
method: z.literal(MySqlConnectionMethod.UsernameAndPassword),
|
||||
credentials: BaseSqlUsernameAndPasswordConnectionSchema
|
||||
})
|
||||
]);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const MySqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection ?? {
|
||||
app: AppConnection.MySql,
|
||||
method: MySqlConnectionMethod.UsernameAndPassword,
|
||||
credentials: {
|
||||
host: "",
|
||||
port: 3306,
|
||||
database: "default",
|
||||
username: "",
|
||||
password: "",
|
||||
sslEnabled: true,
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: undefined
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
|
||||
|
||||
const confirmSubmit = async (formData: FormData) => {
|
||||
if (formData.isPlatformManagedCredentials) {
|
||||
setShowConfirmation(true);
|
||||
return;
|
||||
}
|
||||
|
||||
await onSubmit(formData);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
setSelectedTabIndex(0);
|
||||
handleSubmit(confirmSubmit)(e);
|
||||
}}
|
||||
>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText={`The method you would like to use to connect with ${
|
||||
APP_CONNECTION_MAP[AppConnection.MySql].name
|
||||
}. This field cannot be changed after creation.`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Method"
|
||||
>
|
||||
<Select
|
||||
isDisabled={isUpdate}
|
||||
value={value}
|
||||
onValueChange={(val) => onChange(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{Object.values(MySqlConnectionMethod).map((method) => {
|
||||
return (
|
||||
<SelectItem value={method} key={method}>
|
||||
{getAppConnectionMethodDetails(method).name}{" "}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<SqlConnectionFields
|
||||
isPlatformManagedCredentials={isPlatformManagedCredentials}
|
||||
selectedTabIndex={selectedTabIndex}
|
||||
setSelectedTabIndex={setSelectedTabIndex}
|
||||
/>
|
||||
{isPlatformManagedCredentials ? (
|
||||
<PlatformManagedNoticeBanner />
|
||||
) : (
|
||||
<div className="mt-6 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isDirty}
|
||||
>
|
||||
{isUpdate ? "Update Credentials" : "Connect to Database"}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
<PlatformManagedConfirmationModal
|
||||
onConfirm={() => handleSubmit(onSubmit)()}
|
||||
onOpenChange={setShowConfirmation}
|
||||
isOpen={showConfirmation}
|
||||
/>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|