diff --git a/package.json b/package.json index fd3f85e..8093c3e 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "author": "Infisical Inc, ", "license": "ISC", - "description": "The Infisical SDK provides a convenient way to interact with the Infisical API.", + "description": "The Infisical SDK provides a convenient way to programmatically interact with the Infisical API.", "devDependencies": { "@types/node": "^22.5.1", "tsc": "^2.0.4", diff --git a/src/api/base.ts b/src/api/base.ts index 654c744..7e97c4a 100644 --- a/src/api/base.ts +++ b/src/api/base.ts @@ -30,6 +30,7 @@ export class ApiClient { if (!config._retryCount) config._retryCount = 0; + // handle rate limits and network errors if ( (error.response?.status === 429 || error.response?.status === undefined) && diff --git a/src/api/endpoints/environments.ts b/src/api/endpoints/environments.ts index d62aeb2..40db3bb 100644 --- a/src/api/endpoints/environments.ts +++ b/src/api/endpoints/environments.ts @@ -9,11 +9,7 @@ export class EnvironmentsApi { ): Promise { return this.apiClient.post( `/api/v1/workspace/${data.projectId}/environments`, - { - name: data.name, - slug: data.slug, - position: data.position, - } + data ); } } diff --git a/src/api/endpoints/projects.ts b/src/api/endpoints/projects.ts index 6d75892..47e8559 100644 --- a/src/api/endpoints/projects.ts +++ b/src/api/endpoints/projects.ts @@ -21,11 +21,7 @@ export class ProjectsApi { ): Promise { return this.apiClient.post( `/api/v2/workspace/${data.projectId}/memberships`, - { - emails: data.emails, - usernames: data.usernames, - roleSlugs: data.roleSlugs, - } + data ); } } diff --git a/src/api/endpoints/secrets.ts b/src/api/endpoints/secrets.ts index e35bf04..8a2a2ab 100644 --- a/src/api/endpoints/secrets.ts +++ b/src/api/endpoints/secrets.ts @@ -25,7 +25,7 @@ export class SecretsApi { const { secretName, ...queryParams } = params; return this.apiClient.get( `/api/v3/secrets/raw/${encodeURIComponent(secretName)}`, - { params: queryParams } + { params } ); } diff --git a/src/api/types/dynamic-secrets.ts b/src/api/types/dynamic-secrets.ts index ff69549..ca89850 100644 --- a/src/api/types/dynamic-secrets.ts +++ b/src/api/types/dynamic-secrets.ts @@ -87,3 +87,43 @@ export interface RenewLeaseRequest { export interface RenewLeaseResponse { lease: Lease; } + +export type CreateDynamicSecretOptions = { + provider: TDynamicSecretProvider; + defaultTTL: string; + maxTTL: string; + name: string; + projectSlug: string; + environmentSlug: string; + path?: string; + metadata?: Record; +}; + +export type DeleteDynamicSecretOptions = { + environmentSlug: string; + projectSlug: string; + path?: string; + isForced?: boolean; +}; + +export type CreateDynamicSecretLeaseOptions = { + dynamicSecretName: string; + environmentSlug: string; + projectSlug: string; + path?: string; + ttl?: string; +}; + +export type DeleteDynamicSecretLeaseOptions = { + environmentSlug: string; + projectSlug: string; + path?: string; + isForced?: boolean; +}; + +export type RenewDynamicSecretLeaseOptions = { + environmentSlug: string; + projectSlug: string; + path?: string; + ttl?: string; +}; diff --git a/src/api/types/environments.ts b/src/api/types/environments.ts index 1f30191..f2cb5ee 100644 --- a/src/api/types/environments.ts +++ b/src/api/types/environments.ts @@ -16,5 +16,14 @@ export interface CreateEnvironmentRequest { } export type CreateEnvironmentResponse = { + message: string; + workspace: string; environment: Environment; }; + +export type CreateEnvironmentOptions = { + name: string; + projectId: string; + slug: string; + position?: number; +}; diff --git a/src/api/types/folders.ts b/src/api/types/folders.ts index fcbd705..04687de 100644 --- a/src/api/types/folders.ts +++ b/src/api/types/folders.ts @@ -1,12 +1,14 @@ export interface Folder { id: string; name: string; - path: string; - workspaceId: string; - environment: string; + envId: string; description?: string; createdAt: string; updatedAt: string; + parentId?: string; + isReserved?: boolean; + lastSecretModified?: string; + version?: number; } export interface CreateFolderRequest { @@ -33,3 +35,18 @@ export interface ListFoldersResponse { folders: Folder[]; } +export type CreateFolderOptions = { + name: string; + path: string; + projectId: string; + environment: string; + description?: string; +}; + +export type ListFoldersOptions = { + environment: string; + projectId: string; + path?: string; + recursive?: boolean; + lastSecretModified?: string; +}; diff --git a/src/api/types/projects.ts b/src/api/types/projects.ts index 3cc6046..2cdab07 100644 --- a/src/api/types/projects.ts +++ b/src/api/types/projects.ts @@ -41,3 +41,19 @@ export interface Membership { export interface InviteMembersResponse { memberships: Membership[]; } + +export type CreateProjectOptions = { + projectName: string; + type: string; + projectDescription?: string; + slug?: string; + template?: string; + kmsKeyId?: string; +}; + +export type InviteMemberToProjectOptions = { + projectId: string; + emails?: string[]; + usernames?: string[]; + roleSlugs?: string[]; +}; diff --git a/src/api/types/secrets.ts b/src/api/types/secrets.ts index 484084e..62e4ea8 100644 --- a/src/api/types/secrets.ts +++ b/src/api/types/secrets.ts @@ -1,3 +1,5 @@ +type SecretType = "shared" | "personal"; + export interface Secret { id: string; workspaceId: string; @@ -6,7 +8,21 @@ export interface Secret { secretValue: string; secretComment?: string; secretPath?: string; - type: "shared" | "personal"; + secretValueHidden: boolean; + secretReminderNote?: string; + secretReminderRepeatDays?: number; + skipMultilineEncoding?: boolean; + folderId?: string; + actor?: { + actorId?: string; + name?: string; + actorType?: string; + membershipId?: string; + } + isRotatedSecret: boolean; + rotationId?: string; + secretMetadata?: Record; + type: SecretType; createdAt: string; updatedAt: string; version: number; @@ -29,6 +45,8 @@ export interface ListSecretsResponse { imports?: Array<{ secretPath: string; secrets: Secret[]; + folderId?: string; + environment: string; }>; } @@ -39,7 +57,7 @@ export interface GetSecretRequest { expandSecretReferences?: string; includeImports?: string; secretPath?: string; - type?: "shared" | "personal"; + type?: SecretType; version?: number; viewSecretValue?: string; } @@ -58,7 +76,7 @@ export interface CreateSecretRequest { secretReminderRepeatDays?: number; skipMultilineEncoding?: boolean; tagIds?: string[]; - type?: "shared" | "personal"; + type?: SecretType; } export interface UpdateSecretRequest { @@ -72,7 +90,7 @@ export interface UpdateSecretRequest { secretReminderRepeatDays?: number; skipMultilineEncoding?: boolean; tagIds?: string[]; - type?: "shared" | "personal"; + type?: SecretType; metadata?: Record; } @@ -80,5 +98,58 @@ export interface DeleteSecretRequest { workspaceId: string; environment: string; secretPath?: string; - type?: "shared" | "personal"; + type?: SecretType; } + +export type ListSecretsOptions = { + environment: string; + projectId: string; + expandSecretReferences?: boolean; + includeImports?: boolean; + recursive?: boolean; + secretPath?: string; + tagSlugs?: string[]; + viewSecretValue?: boolean; +}; + +export type GetSecretOptions = { + environment: string; + secretName: string; + expandSecretReferences?: boolean; + includeImports?: boolean; + secretPath?: string; + type?: SecretType; + version?: number; + projectId: string; + viewSecretValue?: boolean; +}; + +export type BaseSecretOptions = { + environment: string; + projectId: string; + secretComment?: string; + secretPath?: string; + secretReminderNote?: string; + secretReminderRepeatDays?: number; + skipMultilineEncoding?: boolean; + tagIds?: string[]; + type?: SecretType; + metadata?: Record; + secretMetadata?: Record[]; +}; + +export type UpdateSecretOptions = { + secretValue?: string; + newSecretName?: string; +} & BaseSecretOptions; + +export type CreateSecretOptions = { + secretValue: string; +} & BaseSecretOptions; + +export type DeleteSecretOptions = { + environment: string; + projectId: string; + secretPath?: string; + type?: SecretType; +}; diff --git a/src/custom/auth.ts b/src/custom/auth.ts index 180df2b..86e759d 100644 --- a/src/custom/auth.ts +++ b/src/custom/auth.ts @@ -27,19 +27,11 @@ export const renewToken = async (apiClient: AuthApi, token?: string) => { }; export default class AuthClient { - #sdkAuthenticator: AuthenticatorFunction; - #apiClient: AuthApi; - #accessToken?: string; - constructor( - authenticator: AuthenticatorFunction, - apiInstance: AuthApi, - accessToken?: string - ) { - this.#sdkAuthenticator = authenticator; - this.#apiClient = apiInstance; - this.#accessToken = accessToken; - } + private sdkAuthenticator: AuthenticatorFunction, + private apiClient: AuthApi, + private _accessToken?: string + ) {} awsIamAuth = { login: async (options?: AwsAuthLoginOptions) => { @@ -53,7 +45,7 @@ export default class AuthClient { } const iamRequest = await performAwsIamLogin(await getAwsRegion()); - const res = await this.#apiClient.awsIamAuthLogin({ + const res = await this.apiClient.awsIamAuthLogin({ iamHttpRequestMethod: iamRequest.iamHttpRequestMethod, iamRequestBody: Buffer.from(iamRequest.iamRequestBody).toString( "base64" @@ -64,7 +56,7 @@ export default class AuthClient { identityId, }); - return this.#sdkAuthenticator(res.accessToken); + return this.sdkAuthenticator(res.accessToken); } catch (err) { throw newInfisicalError(err); } @@ -72,10 +64,10 @@ export default class AuthClient { renew: async () => { try { const refreshedToken = await renewToken( - this.#apiClient, - this.#accessToken + this.apiClient, + this._accessToken ); - return this.#sdkAuthenticator(refreshedToken.accessToken); + return this.sdkAuthenticator(refreshedToken.accessToken); } catch (err) { throw newInfisicalError(err); } @@ -85,8 +77,8 @@ export default class AuthClient { universalAuth = { login: async (options: UniversalAuthLoginRequest) => { try { - const res = await this.#apiClient.universalAuthLogin(options); - return this.#sdkAuthenticator(res.accessToken); + const res = await this.apiClient.universalAuthLogin(options); + return this.sdkAuthenticator(res.accessToken); } catch (err) { throw newInfisicalError(err); } @@ -94,10 +86,10 @@ export default class AuthClient { renew: async () => { try { const refreshedToken = await renewToken( - this.#apiClient, - this.#accessToken + this.apiClient, + this._accessToken ); - return this.#sdkAuthenticator(refreshedToken.accessToken); + return this.sdkAuthenticator(refreshedToken.accessToken); } catch (err) { throw newInfisicalError(err); } @@ -105,6 +97,6 @@ export default class AuthClient { }; accessToken = (token: string) => { - return this.#sdkAuthenticator(token); + return this.sdkAuthenticator(token); }; } diff --git a/src/custom/dynamic-secrets.ts b/src/custom/dynamic-secrets.ts index 50262b5..3250c6c 100644 --- a/src/custom/dynamic-secrets.ts +++ b/src/custom/dynamic-secrets.ts @@ -1,46 +1,13 @@ import { DynamicSecretsApi } from "../api/endpoints/dynamic-secrets"; import { TDynamicSecretProvider } from "./schemas/dynamic-secrets"; import { newInfisicalError } from "./errors"; - -export type CreateDynamicSecretOptions = { - provider: TDynamicSecretProvider; - defaultTTL: string; - maxTTL: string; - name: string; - projectSlug: string; - environmentSlug: string; - path?: string; - metadata?: Record; -}; - -export type DeleteDynamicSecretOptions = { - environmentSlug: string; - projectSlug: string; - path?: string; - isForced?: boolean; -}; - -export type CreateDynamicSecretLeaseOptions = { - dynamicSecretName: string; - environmentSlug: string; - projectSlug: string; - path?: string; - ttl?: string; -}; - -export type DeleteDynamicSecretLeaseOptions = { - environmentSlug: string; - projectSlug: string; - path?: string; - isForced?: boolean; -}; - -export type RenewDynamicSecretLeaseOptions = { - environmentSlug: string; - projectSlug: string; - path?: string; - ttl?: string; -}; +import { + CreateDynamicSecretOptions, + DeleteDynamicSecretOptions, + CreateDynamicSecretLeaseOptions, + DeleteDynamicSecretLeaseOptions, + RenewDynamicSecretLeaseOptions, +} from "../api/types/dynamic-secrets"; export default class DynamicSecretsClient { constructor(private apiClient: DynamicSecretsApi) {} diff --git a/src/custom/environments.ts b/src/custom/environments.ts index 61c41e2..78c39e0 100644 --- a/src/custom/environments.ts +++ b/src/custom/environments.ts @@ -1,12 +1,6 @@ import { EnvironmentsApi } from "../api/endpoints/environments"; import { newInfisicalError } from "./errors"; - -export type CreateEnvironmentOptions = { - name: string; - projectId: string; - slug: string; - position?: number; -}; +import { CreateEnvironmentOptions } from "../api/types/environments"; export default class EnvironmentsClient { constructor(private apiClient: EnvironmentsApi) {} diff --git a/src/custom/errors.ts b/src/custom/errors.ts index 29b8660..8dff1f4 100644 --- a/src/custom/errors.ts +++ b/src/custom/errors.ts @@ -1,3 +1,5 @@ +import axios from "axios"; + export class InfisicalSDKError extends Error { constructor(message: string) { super(message); @@ -22,7 +24,7 @@ export class InfisicalSDKRequestError extends Error { } export const newInfisicalError = (error: any) => { - if (error?.isAxiosError) { + if (axios.isAxiosError(error)) { const data = error?.response?.data; if (data?.message) { @@ -39,6 +41,7 @@ export const newInfisicalError = (error: any) => { } else if (error.message) { return new InfisicalSDKError(error.message); } else if (error.code) { + // If theres no message but a code is present, it's likely to be an aggregation error. This is not specific to Axios, but it falls under the AxiosError type return new InfisicalSDKError(error.code); } else { return new InfisicalSDKError("Request failed with unknown error"); diff --git a/src/custom/folders.ts b/src/custom/folders.ts index 16c7630..c928294 100644 --- a/src/custom/folders.ts +++ b/src/custom/folders.ts @@ -1,21 +1,6 @@ import { FoldersApi } from "../api/endpoints/folders"; import { newInfisicalError } from "./errors"; - -export type CreateFolderOptions = { - name: string; - path: string; - projectId: string; - environment: string; - description?: string; -}; - -export type ListFoldersOptions = { - environment: string; - projectId: string; - path?: string; - recursive?: boolean; - lastSecretModified?: string; -}; +import { CreateFolderOptions, ListFoldersOptions } from "../api/types/folders"; export default class FoldersClient { constructor(private apiClient: FoldersApi) {} diff --git a/src/custom/projects.ts b/src/custom/projects.ts index 56fe135..3083e95 100644 --- a/src/custom/projects.ts +++ b/src/custom/projects.ts @@ -1,21 +1,6 @@ import { ProjectsApi } from "../api/endpoints/projects"; import { newInfisicalError } from "./errors"; - -export type CreateProjectOptions = { - projectName: string; - type: string; - projectDescription?: string; - slug?: string; - template?: string; - kmsKeyId?: string; -}; - -export type InviteMemberToProjectOptions = { - projectId: string; - emails?: string[]; - usernames?: string[]; - roleSlugs?: string[]; -}; +import { CreateProjectOptions, InviteMemberToProjectOptions } from "../api/types/projects"; export default class ProjectsClient { constructor(private apiClient: ProjectsApi) {} diff --git a/src/custom/schemas/dynamic-secrets.ts b/src/custom/schemas/dynamic-secrets.ts index 22c6969..4aa2a23 100644 --- a/src/custom/schemas/dynamic-secrets.ts +++ b/src/custom/schemas/dynamic-secrets.ts @@ -1,301 +1,394 @@ import { z } from "zod"; export enum SqlProviders { - Postgres = "postgres", - MySQL = "mysql2", - Oracle = "oracledb", - MsSQL = "mssql", - SapAse = "sap-ase" + Postgres = "postgres", + MySQL = "mysql2", + Oracle = "oracledb", + MsSQL = "mssql", + SapAse = "sap-ase", } export enum ElasticSearchAuthTypes { - User = "user", - ApiKey = "api-key" + User = "user", + ApiKey = "api-key", } export enum LdapCredentialType { - Dynamic = "dynamic", - Static = "static" + Dynamic = "dynamic", + Static = "static", } export enum TotpConfigType { - URL = "url", - MANUAL = "manual" + URL = "url", + MANUAL = "manual", } export enum TotpAlgorithm { - SHA1 = "sha1", - SHA256 = "sha256", - SHA512 = "sha512" + SHA1 = "sha1", + SHA256 = "sha256", + SHA512 = "sha512", } +const passwordRequirementsSchema = z.object({ + length: z + .number() + .min(1, { message: "Password length must be at least 1" }) + .max(250, { message: "Password length must be at most 250" }), + + required: z.object({ + minUppercase: z.number().min(0).optional(), + minLowercase: z.number().min(0).optional(), + minDigits: z.number().min(0).optional(), + minSymbols: z.number().min(0).optional(), + }), + allowedSymbols: z.string().optional(), +}); + const DynamicSecretRedisDBSchema = z.object({ - host: z.string().trim().toLowerCase(), - port: z.number(), - username: z.string().trim(), // this is often "default". - password: z.string().trim().optional(), - creationStatement: z.string().trim(), - revocationStatement: z.string().trim(), - renewStatement: z.string().trim().optional(), - ca: z.string().optional() + host: z.string().trim().toLowerCase(), + port: z.number(), + username: z.string().trim(), // this is often "default". + password: z.string().trim().optional(), + creationStatement: z.string().trim(), + revocationStatement: z.string().trim(), + renewStatement: z.string().trim().optional(), + ca: z.string().optional(), }); const DynamicSecretAwsElastiCacheSchema = z.object({ - clusterName: z.string().trim().min(1), - accessKeyId: z.string().trim().min(1), - secretAccessKey: z.string().trim().min(1), + clusterName: z.string().trim().min(1), + accessKeyId: z.string().trim().min(1), + secretAccessKey: z.string().trim().min(1), - region: z.string().trim(), - creationStatement: z.string().trim(), - revocationStatement: z.string().trim(), - ca: z.string().optional() + region: z.string().trim(), + creationStatement: z.string().trim(), + revocationStatement: z.string().trim(), + ca: z.string().optional(), }); const DynamicSecretElasticSearchSchema = z.object({ - host: z.string().trim().min(1), - port: z.number(), - roles: z.array(z.string().trim().min(1)).min(1), + host: z.string().trim().min(1), + port: z.number(), + roles: z.array(z.string().trim().min(1)).min(1), - // two auth types "user, apikey" - auth: z.discriminatedUnion("type", [ - z.object({ - type: z.literal(ElasticSearchAuthTypes.User), - username: z.string().trim(), - password: z.string().trim() - }), - z.object({ - type: z.literal(ElasticSearchAuthTypes.ApiKey), - apiKey: z.string().trim(), - apiKeyId: z.string().trim() - }) - ]), + // two auth types "user, apikey" + auth: z.discriminatedUnion("type", [ + z.object({ + type: z.literal(ElasticSearchAuthTypes.User), + username: z.string().trim(), + password: z.string().trim(), + }), + z.object({ + type: z.literal(ElasticSearchAuthTypes.ApiKey), + apiKey: z.string().trim(), + apiKeyId: z.string().trim(), + }), + ]), - ca: z.string().optional() + ca: z.string().optional(), }); const DynamicSecretRabbitMqSchema = z.object({ - host: z.string().trim().min(1), - port: z.number(), - tags: z.array(z.string().trim()).default([]), + host: z.string().trim().min(1), + port: z.number(), + tags: z.array(z.string().trim()).default([]), - username: z.string().trim().min(1), - password: z.string().trim().min(1), + username: z.string().trim().min(1), + password: z.string().trim().min(1), - ca: z.string().optional(), + ca: z.string().optional(), - virtualHost: z.object({ - name: z.string().trim().min(1), - permissions: z.object({ - read: z.string().trim().min(1), - write: z.string().trim().min(1), - configure: z.string().trim().min(1) - }) - }) + virtualHost: z.object({ + name: z.string().trim().min(1), + permissions: z.object({ + read: z.string().trim().min(1), + write: z.string().trim().min(1), + configure: z.string().trim().min(1), + }), + }), }); const DynamicSecretSqlDBSchema = z.object({ - client: z.nativeEnum(SqlProviders), - host: z.string().trim().toLowerCase(), - port: z.number(), - database: z.string().trim(), - username: z.string().trim(), - password: z.string().trim(), - creationStatement: z.string().trim(), - revocationStatement: z.string().trim(), - renewStatement: z.string().trim().optional(), - ca: z.string().optional() + client: z.nativeEnum(SqlProviders), + host: z.string().trim().toLowerCase(), + port: z.number(), + database: z.string().trim(), + username: z.string().trim(), + password: z.string().trim(), + creationStatement: z.string().trim(), + revocationStatement: z.string().trim(), + renewStatement: z.string().trim().optional(), + ca: z.string().optional(), + passwordRequirements: passwordRequirementsSchema.optional(), }); const DynamicSecretCassandraSchema = z.object({ - host: z.string().trim().toLowerCase(), - port: z.number(), - localDataCenter: z.string().trim().min(1), - keyspace: z.string().trim().optional(), - username: z.string().trim(), - password: z.string().trim(), - creationStatement: z.string().trim(), - revocationStatement: z.string().trim(), - renewStatement: z.string().trim().optional(), - ca: z.string().optional() + host: z.string().trim().toLowerCase(), + port: z.number(), + localDataCenter: z.string().trim().min(1), + keyspace: z.string().trim().optional(), + username: z.string().trim(), + password: z.string().trim(), + creationStatement: z.string().trim(), + revocationStatement: z.string().trim(), + renewStatement: z.string().trim().optional(), + ca: z.string().optional(), }); const DynamicSecretSapAseSchema = z.object({ - host: z.string().trim().toLowerCase(), - port: z.number(), - database: z.string().trim(), - username: z.string().trim(), - password: z.string().trim(), - creationStatement: z.string().trim(), - revocationStatement: z.string().trim() + host: z.string().trim().toLowerCase(), + port: z.number(), + database: z.string().trim(), + username: z.string().trim(), + password: z.string().trim(), + creationStatement: z.string().trim(), + revocationStatement: z.string().trim(), }); const DynamicSecretAwsIamSchema = z.object({ - accessKey: z.string().trim().min(1), - secretAccessKey: z.string().trim().min(1), - region: z.string().trim().min(1), - awsPath: z.string().trim().optional(), - permissionBoundaryPolicyArn: z.string().trim().optional(), - policyDocument: z.string().trim().optional(), - userGroups: z.string().trim().optional(), - policyArns: z.string().trim().optional() + accessKey: z.string().trim().min(1), + secretAccessKey: z.string().trim().min(1), + region: z.string().trim().min(1), + awsPath: z.string().trim().optional(), + permissionBoundaryPolicyArn: z.string().trim().optional(), + policyDocument: z.string().trim().optional(), + userGroups: z.string().trim().optional(), + policyArns: z.string().trim().optional(), }); const DynamicSecretMongoAtlasSchema = z.object({ - adminPublicKey: z.string().trim().min(1).describe("Admin user public api key"), - adminPrivateKey: z.string().trim().min(1).describe("Admin user private api key"), - groupId: z.string().trim().min(1).describe("Unique 24-hexadecimal digit string that identifies your project. This is same as project id"), - roles: z - .object({ - collectionName: z.string().optional().describe("Collection on which this role applies."), - databaseName: z.string().min(1).describe("Database to which the user is granted access privileges."), - roleName: z - .string() - .min(1) - .describe( - ' Enum: "atlasAdmin" "backup" "clusterMonitor" "dbAdmin" "dbAdminAnyDatabase" "enableSharding" "read" "readAnyDatabase" "readWrite" "readWriteAnyDatabase" "".Human-readable label that identifies a group of privileges assigned to a database user. This value can either be a built-in role or a custom role.' - ) - }) - .array() - .min(1), - scopes: z - .object({ - name: z - .string() - .min(1) - .describe("Human-readable label that identifies the cluster or MongoDB Atlas Data Lake that this database user can access."), - type: z.string().min(1).describe("Category of resource that this database user can access. Enum: CLUSTER, DATA_LAKE, STREAM") - }) - .array() + adminPublicKey: z + .string() + .trim() + .min(1) + .describe("Admin user public api key"), + adminPrivateKey: z + .string() + .trim() + .min(1) + .describe("Admin user private api key"), + groupId: z + .string() + .trim() + .min(1) + .describe( + "Unique 24-hexadecimal digit string that identifies your project. This is same as project id" + ), + roles: z + .object({ + collectionName: z + .string() + .optional() + .describe("Collection on which this role applies."), + databaseName: z + .string() + .min(1) + .describe("Database to which the user is granted access privileges."), + roleName: z + .string() + .min(1) + .describe( + ' Enum: "atlasAdmin" "backup" "clusterMonitor" "dbAdmin" "dbAdminAnyDatabase" "enableSharding" "read" "readAnyDatabase" "readWrite" "readWriteAnyDatabase" "".Human-readable label that identifies a group of privileges assigned to a database user. This value can either be a built-in role or a custom role.' + ), + }) + .array() + .min(1), + scopes: z + .object({ + name: z + .string() + .min(1) + .describe( + "Human-readable label that identifies the cluster or MongoDB Atlas Data Lake that this database user can access." + ), + type: z + .string() + .min(1) + .describe( + "Category of resource that this database user can access. Enum: CLUSTER, DATA_LAKE, STREAM" + ), + }) + .array(), }); const DynamicSecretMongoDBSchema = z.object({ - host: z.string().min(1).trim().toLowerCase(), - port: z.number().optional(), - username: z.string().min(1).trim(), - password: z.string().min(1).trim(), - database: z.string().min(1).trim(), - ca: z.string().min(1).optional(), - roles: z - .string() - .array() - .min(1) - .describe( - 'Enum: "atlasAdmin" "backup" "clusterMonitor" "dbAdmin" "dbAdminAnyDatabase" "enableSharding" "read" "readAnyDatabase" "readWrite" "readWriteAnyDatabase" "".Human-readable label that identifies a group of privileges assigned to a database user. This value can either be a built-in role or a custom role.' - ) + host: z.string().min(1).trim().toLowerCase(), + port: z.number().optional(), + username: z.string().min(1).trim(), + password: z.string().min(1).trim(), + database: z.string().min(1).trim(), + ca: z.string().min(1).optional(), + roles: z + .string() + .array() + .min(1) + .describe( + 'Enum: "atlasAdmin" "backup" "clusterMonitor" "dbAdmin" "dbAdminAnyDatabase" "enableSharding" "read" "readAnyDatabase" "readWrite" "readWriteAnyDatabase" "".Human-readable label that identifies a group of privileges assigned to a database user. This value can either be a built-in role or a custom role.' + ), }); const DynamicSecretSapHanaSchema = z.object({ - host: z.string().trim().toLowerCase(), - port: z.number(), - username: z.string().trim(), - password: z.string().trim(), - creationStatement: z.string().trim(), - revocationStatement: z.string().trim(), - renewStatement: z.string().trim().optional(), - ca: z.string().optional() + host: z.string().trim().toLowerCase(), + port: z.number(), + username: z.string().trim(), + password: z.string().trim(), + creationStatement: z.string().trim(), + revocationStatement: z.string().trim(), + renewStatement: z.string().trim().optional(), + ca: z.string().optional(), }); const DynamicSecretSnowflakeSchema = z.object({ - accountId: z.string().trim().min(1), - orgId: z.string().trim().min(1), - username: z.string().trim().min(1), - password: z.string().trim().min(1), - creationStatement: z.string().trim().min(1), - revocationStatement: z.string().trim().min(1), - renewStatement: z.string().trim().optional() + accountId: z.string().trim().min(1), + orgId: z.string().trim().min(1), + username: z.string().trim().min(1), + password: z.string().trim().min(1), + creationStatement: z.string().trim().min(1), + revocationStatement: z.string().trim().min(1), + renewStatement: z.string().trim().optional(), }); const AzureEntraIDSchema = z.object({ - tenantId: z.string().trim().min(1), - userId: z.string().trim().min(1), - email: z.string().trim().min(1), - applicationId: z.string().trim().min(1), - clientSecret: z.string().trim().min(1) + tenantId: z.string().trim().min(1), + userId: z.string().trim().min(1), + email: z.string().trim().min(1), + applicationId: z.string().trim().min(1), + clientSecret: z.string().trim().min(1), }); const LdapSchema = z.union([ - z.object({ - url: z.string().trim().min(1), - binddn: z.string().trim().min(1), - bindpass: z.string().trim().min(1), - ca: z.string().optional(), - credentialType: z.literal(LdapCredentialType.Dynamic).optional().default(LdapCredentialType.Dynamic), - creationLdif: z.string().min(1), - revocationLdif: z.string().min(1), - rollbackLdif: z.string().optional() - }), - z.object({ - url: z.string().trim().min(1), - binddn: z.string().trim().min(1), - bindpass: z.string().trim().min(1), - ca: z.string().optional(), - credentialType: z.literal(LdapCredentialType.Static), - rotationLdif: z.string().min(1) - }) + z.object({ + url: z.string().trim().min(1), + binddn: z.string().trim().min(1), + bindpass: z.string().trim().min(1), + ca: z.string().optional(), + credentialType: z + .literal(LdapCredentialType.Dynamic) + .optional() + .default(LdapCredentialType.Dynamic), + creationLdif: z.string().min(1), + revocationLdif: z.string().min(1), + rollbackLdif: z.string().optional(), + }), + z.object({ + url: z.string().trim().min(1), + binddn: z.string().trim().min(1), + bindpass: z.string().trim().min(1), + ca: z.string().optional(), + credentialType: z.literal(LdapCredentialType.Static), + rotationLdif: z.string().min(1), + }), ]); const DynamicSecretTotpSchema = z.discriminatedUnion("configType", [ - z.object({ - configType: z.literal(TotpConfigType.URL), - url: z - .string() - .url() - .trim() - .min(1) - .refine(val => { - const urlObj = new URL(val); - const secret = urlObj.searchParams.get("secret"); + z.object({ + configType: z.literal(TotpConfigType.URL), + url: z + .string() + .url() + .trim() + .min(1) + .refine((val) => { + const urlObj = new URL(val); + const secret = urlObj.searchParams.get("secret"); - return Boolean(secret); - }, "OTP URL must contain secret field") - }), - z.object({ - configType: z.literal(TotpConfigType.MANUAL), - secret: z - .string() - .trim() - .min(1) - .transform(val => val.replace(/\s+/g, "")), - period: z.number().optional(), - algorithm: z.nativeEnum(TotpAlgorithm).optional(), - digits: z.number().optional() - }) + return Boolean(secret); + }, "OTP URL must contain secret field"), + }), + z.object({ + configType: z.literal(TotpConfigType.MANUAL), + secret: z + .string() + .trim() + .min(1) + .transform((val) => val.replace(/\s+/g, "")), + period: z.number().optional(), + algorithm: z.nativeEnum(TotpAlgorithm).optional(), + digits: z.number().optional(), + }), ]); export enum DynamicSecretProviders { - SqlDatabase = "sql-database", - Cassandra = "cassandra", - AwsIam = "aws-iam", - Redis = "redis", - AwsElastiCache = "aws-elasticache", - MongoAtlas = "mongo-db-atlas", - ElasticSearch = "elastic-search", - MongoDB = "mongo-db", - RabbitMq = "rabbit-mq", - AzureEntraID = "azure-entra-id", - Ldap = "ldap", - SapHana = "sap-hana", - Snowflake = "snowflake", - Totp = "totp", - SapAse = "sap-ase" + SqlDatabase = "sql-database", + Cassandra = "cassandra", + AwsIam = "aws-iam", + Redis = "redis", + AwsElastiCache = "aws-elasticache", + MongoAtlas = "mongo-db-atlas", + ElasticSearch = "elastic-search", + MongoDB = "mongo-db", + RabbitMq = "rabbit-mq", + AzureEntraID = "azure-entra-id", + Ldap = "ldap", + SapHana = "sap-hana", + Snowflake = "snowflake", + Totp = "totp", + SapAse = "sap-ase", } const DynamicSecretProviderSchema = z.discriminatedUnion("type", [ - z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema }), - z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }), - z.object({ type: z.literal(DynamicSecretProviders.SapAse), inputs: DynamicSecretSapAseSchema }), - z.object({ type: z.literal(DynamicSecretProviders.AwsIam), inputs: DynamicSecretAwsIamSchema }), - z.object({ type: z.literal(DynamicSecretProviders.Redis), inputs: DynamicSecretRedisDBSchema }), - z.object({ type: z.literal(DynamicSecretProviders.SapHana), inputs: DynamicSecretSapHanaSchema }), - z.object({ type: z.literal(DynamicSecretProviders.AwsElastiCache), inputs: DynamicSecretAwsElastiCacheSchema }), - z.object({ type: z.literal(DynamicSecretProviders.MongoAtlas), inputs: DynamicSecretMongoAtlasSchema }), - z.object({ type: z.literal(DynamicSecretProviders.ElasticSearch), inputs: DynamicSecretElasticSearchSchema }), - z.object({ type: z.literal(DynamicSecretProviders.MongoDB), inputs: DynamicSecretMongoDBSchema }), - z.object({ type: z.literal(DynamicSecretProviders.RabbitMq), inputs: DynamicSecretRabbitMqSchema }), - z.object({ type: z.literal(DynamicSecretProviders.AzureEntraID), inputs: AzureEntraIDSchema }), - z.object({ type: z.literal(DynamicSecretProviders.Ldap), inputs: LdapSchema }), - z.object({ type: z.literal(DynamicSecretProviders.Snowflake), inputs: DynamicSecretSnowflakeSchema }), - z.object({ type: z.literal(DynamicSecretProviders.Totp), inputs: DynamicSecretTotpSchema }) + z.object({ + type: z.literal(DynamicSecretProviders.SqlDatabase), + inputs: DynamicSecretSqlDBSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.Cassandra), + inputs: DynamicSecretCassandraSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.SapAse), + inputs: DynamicSecretSapAseSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.AwsIam), + inputs: DynamicSecretAwsIamSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.Redis), + inputs: DynamicSecretRedisDBSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.SapHana), + inputs: DynamicSecretSapHanaSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.AwsElastiCache), + inputs: DynamicSecretAwsElastiCacheSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.MongoAtlas), + inputs: DynamicSecretMongoAtlasSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.ElasticSearch), + inputs: DynamicSecretElasticSearchSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.MongoDB), + inputs: DynamicSecretMongoDBSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.RabbitMq), + inputs: DynamicSecretRabbitMqSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.AzureEntraID), + inputs: AzureEntraIDSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.Ldap), + inputs: LdapSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.Snowflake), + inputs: DynamicSecretSnowflakeSchema, + }), + z.object({ + type: z.literal(DynamicSecretProviders.Totp), + inputs: DynamicSecretTotpSchema, + }), ]); -export type TDynamicSecretProvider = z.infer; +export type TDynamicSecretProvider = z.infer< + typeof DynamicSecretProviderSchema +>; diff --git a/src/custom/secrets.ts b/src/custom/secrets.ts index 66e0609..98a4886 100644 --- a/src/custom/secrets.ts +++ b/src/custom/secrets.ts @@ -1,61 +1,7 @@ import { SecretsApi } from "../api/endpoints/secrets"; import { Secret } from "../api/types"; import { newInfisicalError } from "./errors"; - -type SecretType = "shared" | "personal"; - -type ListSecretsOptions = { - environment: string; - projectId: string; - expandSecretReferences?: boolean; - includeImports?: boolean; - recursive?: boolean; - secretPath?: string; - tagSlugs?: string[]; - viewSecretValue?: boolean; -}; - -type GetSecretOptions = { - environment: string; - secretName: string; - expandSecretReferences?: boolean; - includeImports?: boolean; - secretPath?: string; - type?: SecretType; - version?: number; - projectId: string; - viewSecretValue?: boolean; -}; - -type BaseSecretOptions = { - environment: string; - projectId: string; - secretComment?: string; - secretPath?: string; - secretReminderNote?: string; - secretReminderRepeatDays?: number; - skipMultilineEncoding?: boolean; - tagIds?: string[]; - type?: SecretType; - metadata?: Record; - secretMetadata?: Record[]; -}; - -export type UpdateSecretOptions = { - secretValue?: string; - newSecretName?: string; -} & BaseSecretOptions; - -export type CreateSecretOptions = { - secretValue: string; -} & BaseSecretOptions; - -export type DeleteSecretOptions = { - environment: string; - projectId: string; - secretPath?: string; - type?: SecretType; -}; +import { ListSecretsOptions, GetSecretOptions, UpdateSecretOptions, CreateSecretOptions, DeleteSecretOptions } from "../api/types/secrets"; const convertBool = (value?: boolean) => (value ? "true" : "false"); @@ -100,6 +46,11 @@ export default class SecretsClient { if (imports) { for (const imp of imports) { for (const importedSecret of imp.secrets) { + // CASE: We need to ensure that the imported values don't override the "base" secrets. + // Priority order is: + // Local/Preset variables -> Actual secrets -> Imported secrets (high->low) + + // Check if the secret already exists in the secrets list if (!secrets.find((s) => s.secretKey === importedSecret.secretKey)) { secrets.push({ ...importedSecret, diff --git a/src/index.ts b/src/index.ts index 65918bf..e997fc4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -97,10 +97,3 @@ export { TDynamicSecretProvider, DynamicSecretProviders, } from "./custom/schemas"; - -// Export domain-specific types -export * from "./custom/secrets"; -export * from "./custom/dynamic-secrets"; -export * from "./custom/environments"; -export * from "./custom/projects"; -export * from "./custom/folders";