Merge pull request #19 from Infisical/feat/moveSdkToAxios

Refactor: openapi-generator-cli to Axios
This commit is contained in:
carlosmonastyrski
2025-05-07 20:34:40 -03:00
committed by GitHub
29 changed files with 2156 additions and 2880 deletions

View File

@@ -28,9 +28,6 @@ jobs:
- name: Set NPM version - name: Set NPM version
run: npm version ${{ github.ref_name }} --allow-same-version --no-git-tag-version run: npm version ${{ github.ref_name }} --allow-same-version --no-git-tag-version
- name: Build API client
run: npm run generate-api:infisical
- name: Build SDK - name: Build SDK
run: npm run build run: npm run build

View File

@@ -42,6 +42,10 @@ The SDK methods are organized into the following high-level categories:
1. `auth`: Handles authentication methods. 1. `auth`: Handles authentication methods.
2. `secrets`: Manages CRUD operations for secrets. 2. `secrets`: Manages CRUD operations for secrets.
3. `dynamicSecrets`: Manages dynamic secrets and leases.
4. `projects`: Creates and manages projects.
5. `environments`: Creates and manages environments.
6. `folders`: Creates and manages folders.
### `auth` ### `auth`
@@ -102,7 +106,6 @@ await client.auth().awsIamAuth.renew();
``` ```
### `secrets` ### `secrets`
This sub-class handles operations related to secrets: This sub-class handles operations related to secrets:
@@ -132,7 +135,7 @@ const allSecrets = await client.secrets().listSecrets({
- `tagFilters` (string[], optional): Tags to filter secrets. - `tagFilters` (string[], optional): Tags to filter secrets.
**Returns:** **Returns:**
- `ApiV3SecretsRawGet200Response`: The response containing the list of secrets. - `ListSecretsResponse`: The response containing the list of secrets.
#### List secrets with imports #### List secrets with imports
@@ -159,7 +162,7 @@ const allSecrets = await client.secrets().listSecretsWithImports({
- `tagFilters` (string[], optional): Tags to filter secrets. - `tagFilters` (string[], optional): Tags to filter secrets.
**Returns:** **Returns:**
- `ApiV1DashboardSecretsOverviewGet200ResponseSecretsInner`: The response containing the list of secrets, with imports. - `Secret[]`: Returns the list of secrets objects, with imports.
@@ -195,7 +198,7 @@ const allSecrets = await client.secrets().listSecretsWithImports({
- `type` (personal | shared, optional): Which type of secret to create. - `type` (personal | shared, optional): Which type of secret to create.
**Returns:** **Returns:**
- `ApiV3SecretsRawSecretNamePost200Response`: The response after creating the secret. - `CreateSecretResponse`: The response after creating the secret.
#### Update Secret #### Update Secret
@@ -235,7 +238,7 @@ const updatedSecret = await client.secrets().updateSecret("SECRET_TO_UPDATE", {
- `metadata` (object, optional): Assign additional details to the secret, accessible through the API. - `metadata` (object, optional): Assign additional details to the secret, accessible through the API.
**Returns:** **Returns:**
- `ApiV3SecretsRawSecretNamePost200Response`: The response after updating the secret. - `UpdateSecretResponse`: The response after updating the secret.
#### Get Secret by Name #### Get Secret by Name
@@ -266,7 +269,7 @@ const updatedSecret = await client.secrets().updateSecret("SECRET_TO_UPDATE", {
**Returns:** **Returns:**
- `ApiV3SecretsRawSecretNameGet200Response`: The response containing the secret. - `Secret`: Returns the secret object.
#### Delete Secret by Name #### Delete Secret by Name
@@ -288,7 +291,7 @@ const deletedSecret = await client.secrets().deleteSecret("SECRET_TO_DELETE", {
- `type` (personal | shared, optional): The type of secret to delete. - `type` (personal | shared, optional): The type of secret to delete.
**Returns:** **Returns:**
- `ApiV3SecretsRawSecretNamePost200Response`: The response after deleting the secret. - `DeleteSecretResponse`: The response after deleting the secret.
@@ -338,7 +341,7 @@ console.log(dynamicSecret);
``` ```
**Returns:** **Returns:**
- `ApiV1DynamicSecretsPost200Response['dynamicSecret']`: The response after creating the dynamic secret - `DynamicSecret`: The created dynamic secret.
#### Delete a dynamic secret #### Delete a dynamic secret
@@ -359,7 +362,7 @@ const deletedDynamicSecret = await client.dynamicSecrets().delete("dynamic-secre
- `environment` (str): The environment in which to delete the secret. - `environment` (str): The environment in which to delete the secret.
**Returns:** **Returns:**
- `ApiV1DynamicSecretsDelete200Response['dynamicSecret']`: The response after deleting the dynamic secret - `DynamicSecret`: The deleted dynamic secret.
### `dynamicSecrets.leases` ### `dynamicSecrets.leases`
In this section you'll learn how to work with dynamic secret leases In this section you'll learn how to work with dynamic secret leases
@@ -389,7 +392,7 @@ console.log(lease);
- `ttl` (string, optional): A [vercel/ms](https://github.com/vercel/ms) encoded string representation of how long the lease credentials should be valid for. This will default to the dynamic secret's default TTL if not specified. - `ttl` (string, optional): A [vercel/ms](https://github.com/vercel/ms) encoded string representation of how long the lease credentials should be valid for. This will default to the dynamic secret's default TTL if not specified.
**Returns:** **Returns:**
- `ApiV1DynamicSecretsLeasesPost200Response`: The dynamic secret lease result. - `CreateLeaseResponse`: The dynamic secret lease result.
#### Delete a lease #### Delete a lease
@@ -411,7 +414,7 @@ const deletedLease = await client.dynamicSecrets().leases.delete(newLease.lease.
- `isForced` (bool, optional): Wether or not to forcefully delete the lease. This can't guarantee that the lease will be deleted from the external provider, and is potentially unsafe for sensitive dynamic secrets. - `isForced` (bool, optional): Wether or not to forcefully delete the lease. This can't guarantee that the lease will be deleted from the external provider, and is potentially unsafe for sensitive dynamic secrets.
**Returns:** **Returns:**
- `ApiV1DynamicSecretsLeasesLeaseIdDelete200Response`: The deleted lease result. - `DeleteLeaseResponse`: The deleted lease result.
#### Renew a lease #### Renew a lease
@@ -435,7 +438,7 @@ const renewedLease = await client.dynamicSecrets().leases.renew(newLease.lease.i
- `ttl` (string, optional): A [vercel/ms](https://github.com/vercel/ms) encoded string representation of how long the lease credentials should be valid for. This will default to the dynamic secret's default TTL if not specified. - `ttl` (string, optional): A [vercel/ms](https://github.com/vercel/ms) encoded string representation of how long the lease credentials should be valid for. This will default to the dynamic secret's default TTL if not specified.
**Returns:** **Returns:**
- `ApiV1DynamicSecretsLeasesLeaseIdDelete200Response`: The renewed lease response _(doesn't contain new credentials)_. - `RenewLeaseResponse`: The renewed lease response _(doesn't contain new credentials)_.
### `projects` ### `projects`
@@ -461,7 +464,7 @@ const project = await client.projects().create({
- `kmsKeyId` (string): The ID of the KMS key to use for the project. Will use the Infisical KMS by default. - `kmsKeyId` (string): The ID of the KMS key to use for the project. Will use the Infisical KMS by default.
**Returns:** **Returns:**
- `ApiV1WorkspaceWorkspaceIdGet200ResponseWorkspace`: The project that was created. - `Project`: The project that was created.
#### Invite members to a project #### Invite members to a project
@@ -484,7 +487,7 @@ const memberships = await client.projects().inviteMembers({
- `roleSlugs`: (string[]): An array of role slugs to assign to the members. If not specified, this will default to `member`. - `roleSlugs`: (string[]): An array of role slugs to assign to the members. If not specified, this will default to `member`.
**Returns:** **Returns:**
- `ApiV1OrganizationAdminProjectsProjectIdGrantAdminAccessPost200ResponseMembership`: An array of the created project memberships. - `Membership[]`: An array of the created project memberships.
### `environments` ### `environments`
@@ -506,7 +509,7 @@ const environment = await client.environments().create({
- `position` (number): An optional position of the environment to be created. The position is used in the Infisical UI to display environments in order. Environments with the lowest position come first. - `position` (number): An optional position of the environment to be created. The position is used in the Infisical UI to display environments in order. Environments with the lowest position come first.
**Returns:** **Returns:**
- `ApiV1WorkspaceWorkspaceIdEnvironmentsEnvIdGet200ResponseEnvironment`: The environment that was created. - `Environment`: The environment that was created.
#### Create a new folder #### Create a new folder
@@ -528,4 +531,24 @@ const folder = await client.folders().create({
- `description` (string): An optional folder description. - `description` (string): An optional folder description.
**Returns:** **Returns:**
- `ApiV1FoldersPost200ResponseFolder`: The folder that was created. - `Folder`: The folder that was created.
#### List folders
```typescript
const folders = await client.folders().listFolders({
environment: "dev",
projectId: "<your-project-id>",
path: "/foo/bar", // Optional
recursive: false // Optional
});
```
**Parameters:**
- `environment` (string): The slug of the environment to list folders within.
- `projectId` (string): The ID of the project to list folders within.
- `path` (string): The path to list folders within. Defaults to `/`, which is the root folder.
- `recursive` (boolean): An optional flag to list folders recursively. Defaults to `false`.
**Returns:**
- `Folder[]`: An array of folders.

View File

@@ -1,7 +0,0 @@
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "7.8.0"
}
}

2503
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,6 @@
"lib" "lib"
], ],
"scripts": { "scripts": {
"generate-api:infisical": "openapi-generator-cli generate -i https://app.infisical.com/api/docs/json -g typescript-axios -o ./src/infisicalapi_client --skip-validate-spec --additional-properties=useSingleRequestParameter=true,withSeparateModelsAndApi=true,apiPackage=server,modelPackage=model --openapi-normalizer REFACTOR_ALLOF_WITH_PROPERTIES_ONLY=true && npm run post-generate-api",
"generate-api:infisical-dev": "openapi-generator-cli generate -i http://localhost:8080/api/docs/json -g typescript-axios -o ./src/infisicalapi_client --skip-validate-spec --additional-properties=useSingleRequestParameter=true,withSeparateModelsAndApi=true,apiPackage=server,modelPackage=model --openapi-normalizer REFACTOR_ALLOF_WITH_PROPERTIES_ONLY=true && npm run post-generate-api",
"post-generate-api": "rm ./src/infisicalapi_client/git_push.sh",
"build": "tsup src/index.ts --out-dir lib --dts --format cjs,esm --tsconfig tsconfig.json --no-splitting" "build": "tsup src/index.ts --out-dir lib --dts --format cjs,esm --tsconfig tsconfig.json --no-splitting"
}, },
"keywords": [ "keywords": [
@@ -20,13 +17,12 @@
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/infisical/infisical-node-sdk" "url": "git+https://github.com/infisical/infisical-node-sdk.git"
}, },
"author": "Infisical Inc, <daniel@infisical.com>", "author": "Infisical Inc, <daniel@infisical.com>",
"license": "ISC", "license": "ISC",
"description": "", "description": "The Infisical SDK provides a convenient way to programmatically interact with the Infisical API.",
"devDependencies": { "devDependencies": {
"@openapitools/openapi-generator-cli": "^2.13.5",
"@types/node": "^22.5.1", "@types/node": "^22.5.1",
"tsc": "^2.0.4", "tsc": "^2.0.4",
"tsup": "^8.2.4" "tsup": "^8.2.4"
@@ -36,5 +32,14 @@
"axios": "^1.7.5", "axios": "^1.7.5",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"zod": "^3.23.8" "zod": "^3.23.8"
} },
"directories": {
"lib": "lib",
"test": "test"
},
"types": "./lib/index.d.ts",
"bugs": {
"url": "https://github.com/infisical/infisical-node-sdk/issues"
},
"homepage": "https://github.com/infisical/infisical-node-sdk#readme"
} }

93
src/api/base.ts Normal file
View File

@@ -0,0 +1,93 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
export interface ApiClientConfig {
baseURL: string;
headers?: Record<string, string>;
timeout?: number;
}
export class ApiClient {
private client: AxiosInstance;
constructor(config: ApiClientConfig) {
this.client = axios.create({
baseURL: config.baseURL,
headers: config.headers || {},
timeout: config.timeout || 10000,
});
this.setupRetryInterceptor();
}
private setupRetryInterceptor() {
const maxRetries = 4;
const initialRetryDelay = 1000;
const backoffFactor = 2;
this.client.interceptors.response.use(null, (error) => {
const config = error?.config;
if (!config) return Promise.reject(error);
if (!config._retryCount) config._retryCount = 0;
// handle rate limits and network errors
if (
(error.response?.status === 429 ||
error.response?.status === undefined) &&
config._retryCount < maxRetries
) {
config._retryCount++;
const baseDelay =
initialRetryDelay * Math.pow(backoffFactor, config._retryCount - 1);
const jitter = baseDelay * 0.2;
const exponentialDelay = baseDelay + (Math.random() * 2 - 1) * jitter;
return new Promise((resolve) => {
setTimeout(() => resolve(this.client(config)), exponentialDelay);
});
}
return Promise.reject(error);
});
}
public setAccessToken(token: string) {
this.client.defaults.headers.common["Authorization"] = `Bearer ${token}`;
}
public async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response: AxiosResponse<T> = await this.client.get(url, config);
return response.data;
}
public async post<T>(
url: string,
data?: any,
config?: AxiosRequestConfig
): Promise<T> {
const response: AxiosResponse<T> = await this.client.post(
url,
data,
config
);
return response.data;
}
public async patch<T>(
url: string,
data?: any,
config?: AxiosRequestConfig
): Promise<T> {
const response: AxiosResponse<T> = await this.client.patch(
url,
data,
config
);
return response.data;
}
public async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response: AxiosResponse<T> = await this.client.delete(url, config);
return response.data;
}
}

38
src/api/endpoints/auth.ts Normal file
View File

@@ -0,0 +1,38 @@
import { ApiClient } from "../base";
import {
UniversalAuthLoginRequest,
UniversalAuthLoginResponse,
AwsIamAuthLoginRequest,
AwsIamAuthLoginResponse,
TokenRenewRequest,
TokenRenewResponse,
} from "../types";
export class AuthApi {
constructor(private apiClient: ApiClient) {}
async universalAuthLogin(
data: UniversalAuthLoginRequest
): Promise<UniversalAuthLoginResponse> {
return this.apiClient.post<UniversalAuthLoginResponse>(
"/api/v1/auth/universal-auth/login",
data
);
}
async awsIamAuthLogin(
data: AwsIamAuthLoginRequest
): Promise<AwsIamAuthLoginResponse> {
return this.apiClient.post<AwsIamAuthLoginResponse>(
"/api/v1/auth/aws-auth/login",
data
);
}
async renewToken(data: TokenRenewRequest): Promise<TokenRenewResponse> {
return this.apiClient.post<TokenRenewResponse>(
"/api/v1/auth/token/renew",
data
);
}
}

View File

@@ -0,0 +1,65 @@
import { ApiClient } from "../base";
import {
CreateDynamicSecretRequest,
CreateDynamicSecretResponse,
DeleteDynamicSecretRequest,
DeleteDynamicSecretResponse,
CreateLeaseRequest,
CreateLeaseResponse,
DeleteLeaseRequest,
DeleteLeaseResponse,
RenewLeaseRequest,
RenewLeaseResponse,
} from "../types";
export class DynamicSecretsApi {
constructor(private apiClient: ApiClient) {}
async create(
data: CreateDynamicSecretRequest
): Promise<CreateDynamicSecretResponse> {
return this.apiClient.post<CreateDynamicSecretResponse>(
"/api/v1/dynamic-secrets",
data
);
}
async delete(
secretName: string,
data: DeleteDynamicSecretRequest
): Promise<DeleteDynamicSecretResponse> {
return this.apiClient.delete<DeleteDynamicSecretResponse>(
`/api/v1/dynamic-secrets/${encodeURIComponent(secretName)}`,
{ data }
);
}
leases = {
create: async (data: CreateLeaseRequest): Promise<CreateLeaseResponse> => {
return this.apiClient.post<CreateLeaseResponse>(
"/api/v1/dynamic-secrets/leases",
data
);
},
delete: async (
leaseId: string,
data: DeleteLeaseRequest
): Promise<DeleteLeaseResponse> => {
return this.apiClient.delete<DeleteLeaseResponse>(
`/api/v1/dynamic-secrets/leases/${leaseId}`,
{ data }
);
},
renew: async (
leaseId: string,
data: RenewLeaseRequest
): Promise<RenewLeaseResponse> => {
return this.apiClient.post<RenewLeaseResponse>(
`/api/v1/dynamic-secrets/leases/${leaseId}/renew`,
data
);
},
};
}

View File

@@ -0,0 +1,15 @@
import { ApiClient } from "../base";
import { CreateEnvironmentRequest, CreateEnvironmentResponse } from "../types";
export class EnvironmentsApi {
constructor(private apiClient: ApiClient) {}
async create(
data: CreateEnvironmentRequest
): Promise<CreateEnvironmentResponse> {
return this.apiClient.post<CreateEnvironmentResponse>(
`/api/v1/workspace/${data.projectId}/environments`,
data
);
}
}

View File

@@ -0,0 +1,16 @@
import { ApiClient } from "../base";
import { CreateFolderRequest, CreateFolderResponse, ListFoldersRequest, ListFoldersResponse } from "../types";
export class FoldersApi {
constructor(private apiClient: ApiClient) {}
async create(data: CreateFolderRequest): Promise<CreateFolderResponse> {
return this.apiClient.post<CreateFolderResponse>("/api/v1/folders", data);
}
async listFolders(queryParams: ListFoldersRequest): Promise<ListFoldersResponse> {
return this.apiClient.get<ListFoldersResponse>("/api/v1/folders", {
params: queryParams
});
}
}

View File

@@ -0,0 +1,27 @@
import { ApiClient } from "../base";
import {
CreateProjectRequest,
CreateProjectResponse,
InviteMembersRequest,
InviteMembersResponse,
} from "../types";
export class ProjectsApi {
constructor(private apiClient: ApiClient) {}
async create(data: CreateProjectRequest): Promise<CreateProjectResponse> {
return this.apiClient.post<CreateProjectResponse>(
"/api/v2/workspace",
data
);
}
async inviteMembers(
data: InviteMembersRequest
): Promise<InviteMembersResponse> {
return this.apiClient.post<InviteMembersResponse>(
`/api/v2/workspace/${data.projectId}/memberships`,
data
);
}
}

View File

@@ -0,0 +1,61 @@
import { ApiClient } from "../base";
import {
ListSecretsRequest,
ListSecretsResponse,
GetSecretRequest,
GetSecretResponse,
CreateSecretRequest,
CreateSecretResponse,
UpdateSecretRequest,
UpdateSecretResponse,
DeleteSecretRequest,
DeleteSecretResponse,
} from "../types";
export class SecretsApi {
constructor(private apiClient: ApiClient) {}
async listSecrets(params: ListSecretsRequest): Promise<ListSecretsResponse> {
return this.apiClient.get<ListSecretsResponse>("/api/v3/secrets/raw", {
params,
});
}
async getSecret(params: GetSecretRequest): Promise<GetSecretResponse> {
const { secretName, ...queryParams } = params;
return this.apiClient.get<GetSecretResponse>(
`/api/v3/secrets/raw/${encodeURIComponent(secretName)}`,
{ params }
);
}
async createSecret(
secretName: string,
data: CreateSecretRequest
): Promise<CreateSecretResponse> {
return this.apiClient.post<CreateSecretResponse>(
`/api/v3/secrets/raw/${encodeURIComponent(secretName)}`,
data
);
}
async updateSecret(
secretName: string,
data: UpdateSecretRequest
): Promise<UpdateSecretResponse> {
return this.apiClient.patch<UpdateSecretResponse>(
`/api/v3/secrets/raw/${encodeURIComponent(secretName)}`,
data
);
}
async deleteSecret(
secretName: string,
data: DeleteSecretRequest
): Promise<DeleteSecretResponse> {
return this.apiClient.delete<DeleteSecretResponse>(
`/api/v3/secrets/raw/${encodeURIComponent(secretName)}`,
{ data }
);
}
}

30
src/api/types/auth.ts Normal file
View File

@@ -0,0 +1,30 @@
export interface UniversalAuthLoginRequest {
clientId: string;
clientSecret: string;
}
export interface UniversalAuthLoginResponse {
accessToken: string;
expiresIn: number;
}
export interface AwsIamAuthLoginRequest {
identityId: string;
iamHttpRequestMethod: string;
iamRequestBody: string;
iamRequestHeaders: string;
}
export interface AwsIamAuthLoginResponse {
accessToken: string;
expiresIn: number;
}
export interface TokenRenewRequest {
accessToken: string;
}
export interface TokenRenewResponse {
accessToken: string;
expiresIn: number;
}

View File

@@ -0,0 +1,129 @@
import { DynamicSecretProviders } from "../../custom/schemas";
import { TDynamicSecretProvider } from "../../custom/schemas";
export interface CreateDynamicSecretRequest {
provider: TDynamicSecretProvider;
defaultTTL: string;
maxTTL: string;
name: string;
projectSlug: string;
environmentSlug: string;
}
export interface DynamicSecret {
id: string;
name: string;
defaultTTL: string;
maxTTL: string;
provider: {
type: DynamicSecretProviders;
inputs: Record<string, any>;
};
createdAt: string;
updatedAt: string;
version: number;
type: string;
folderId: string;
status: string;
statusDetails: string;
projectGatewayId: string;
metadata: Record<string, any>;
}
export interface CreateDynamicSecretResponse {
dynamicSecret: DynamicSecret;
}
export interface DeleteDynamicSecretRequest {
environmentSlug: string;
projectSlug: string;
path?: string;
isForced?: boolean;
}
export interface DeleteDynamicSecretResponse {
dynamicSecret: DynamicSecret;
}
export interface CreateLeaseRequest {
dynamicSecretName: string;
environmentSlug: string;
projectSlug: string;
path?: string;
ttl?: string;
}
export interface Lease {
id: string;
dynamicSecretId: string;
data: Record<string, any>;
expiresAt: string;
createdAt: string;
updatedAt: string;
}
export interface CreateLeaseResponse {
lease: Lease;
}
export interface DeleteLeaseRequest {
environmentSlug: string;
projectSlug: string;
path?: string;
isForced?: boolean;
}
export interface DeleteLeaseResponse {
lease: Lease;
}
export interface RenewLeaseRequest {
environmentSlug: string;
projectSlug: string;
path?: string;
ttl?: string;
}
export interface RenewLeaseResponse {
lease: Lease;
}
export type CreateDynamicSecretOptions = {
provider: TDynamicSecretProvider;
defaultTTL: string;
maxTTL: string;
name: string;
projectSlug: string;
environmentSlug: string;
path?: string;
metadata?: Record<string, any>;
};
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;
};

View File

@@ -0,0 +1,29 @@
export interface Environment {
id: string;
name: string;
slug: string;
position: number;
projectId: string;
createdAt: string;
updatedAt: string;
}
export interface CreateEnvironmentRequest {
name: string;
projectId: string;
slug: string;
position?: number;
}
export type CreateEnvironmentResponse = {
message: string;
workspace: string;
environment: Environment;
};
export type CreateEnvironmentOptions = {
name: string;
projectId: string;
slug: string;
position?: number;
};

52
src/api/types/folders.ts Normal file
View File

@@ -0,0 +1,52 @@
export interface Folder {
id: string;
name: string;
envId: string;
description?: string;
createdAt: string;
updatedAt: string;
parentId?: string;
isReserved?: boolean;
lastSecretModified?: string;
version?: number;
}
export interface CreateFolderRequest {
name: string;
path: string;
workspaceId: string;
environment: string;
description?: string;
}
export interface ListFoldersRequest {
environment: string;
workspaceId: string;
path?: string;
recursive?: boolean;
lastSecretModified?: string;
}
export interface CreateFolderResponse {
folder: Folder;
}
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;
};

26
src/api/types/index.ts Normal file
View File

@@ -0,0 +1,26 @@
import { Secret } from "./secrets";
export * from "./auth";
export * from "./secrets";
export * from "./dynamic-secrets";
export * from "./environments";
export * from "./projects";
export * from "./folders";
export interface ApiResponse<T> {
statusCode: number;
message: string;
data: T;
}
export interface CreateSecretResponse {
secret: Secret;
}
export interface UpdateSecretResponse {
secret: Secret;
}
export interface DeleteSecretResponse {
secret: Secret;
}

59
src/api/types/projects.ts Normal file
View File

@@ -0,0 +1,59 @@
export interface Project {
id: string;
name: string;
slug: string;
description?: string;
type: string;
createdAt: string;
updatedAt: string;
}
export interface CreateProjectRequest {
projectName: string;
type: string;
projectDescription?: string;
slug?: string;
template?: string;
kmsKeyId?: string;
}
export interface CreateProjectResponse {
project: Project;
}
export interface InviteMembersRequest {
projectId: string;
emails?: string[];
usernames?: string[];
roleSlugs?: string[];
}
export interface Membership {
id: string;
userId: string;
projectId: string;
role: string;
status: string;
createdAt: string;
updatedAt: string;
}
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[];
};

158
src/api/types/secrets.ts Normal file
View File

@@ -0,0 +1,158 @@
export enum SecretType {
Shared = "shared",
Personal = "personal"
}
export interface Secret {
id: string;
workspaceId: string;
environment: string;
secretKey: string;
secretValue: string;
secretComment?: string;
secretPath?: string;
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<string, any>;
type: SecretType;
createdAt: string;
updatedAt: string;
version: number;
tags: string[];
}
export interface ListSecretsRequest {
workspaceId: string;
environment: string;
expandSecretReferences?: string;
includeImports?: string;
recursive?: string;
secretPath?: string;
tagSlugs?: string;
viewSecretValue?: string;
}
export interface ListSecretsResponse {
secrets: Secret[];
imports?: Array<{
secretPath: string;
secrets: Secret[];
folderId?: string;
environment: string;
}>;
}
export interface GetSecretRequest {
secretName: string;
workspaceId: string;
environment: string;
expandSecretReferences?: string;
includeImports?: string;
secretPath?: string;
type?: SecretType;
version?: number;
viewSecretValue?: string;
}
export interface GetSecretResponse {
secret: Secret;
}
export interface CreateSecretRequest {
workspaceId: string;
environment: string;
secretValue: string;
secretComment?: string;
secretPath?: string;
secretReminderNote?: string;
secretReminderRepeatDays?: number;
skipMultilineEncoding?: boolean;
tagIds?: string[];
type?: SecretType;
}
export interface UpdateSecretRequest {
workspaceId: string;
environment: string;
secretValue?: string;
newSecretName?: string;
secretComment?: string;
secretPath?: string;
secretReminderNote?: string;
secretReminderRepeatDays?: number;
skipMultilineEncoding?: boolean;
tagIds?: string[];
type?: SecretType;
metadata?: Record<string, any>;
}
export interface DeleteSecretRequest {
workspaceId: string;
environment: string;
secretPath?: string;
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<string, any>;
secretMetadata?: Record<string, any>[];
};
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;
};

View File

@@ -1,6 +1,6 @@
import { InfisicalSDK } from ".."; import { InfisicalSDK } from "..";
import { ApiV1AuthUniversalAuthLoginPostRequest } from "../infisicalapi_client"; import { AuthApi } from "../api/endpoints/auth";
import { DefaultApi as InfisicalApi } from "../infisicalapi_client"; import { UniversalAuthLoginRequest } from "../api/types";
import { MACHINE_IDENTITY_ID_ENV_NAME } from "./constants"; import { MACHINE_IDENTITY_ID_ENV_NAME } from "./constants";
import { InfisicalSDKError, newInfisicalError } from "./errors"; import { InfisicalSDKError, newInfisicalError } from "./errors";
import { getAwsRegion, performAwsIamLogin } from "./util"; import { getAwsRegion, performAwsIamLogin } from "./util";
@@ -8,97 +8,95 @@ import { getAwsRegion, performAwsIamLogin } from "./util";
type AuthenticatorFunction = (accessToken: string) => InfisicalSDK; type AuthenticatorFunction = (accessToken: string) => InfisicalSDK;
type AwsAuthLoginOptions = { type AwsAuthLoginOptions = {
identityId?: string; identityId?: string;
}; };
export const renewToken = async (apiClient: InfisicalApi, token?: string) => { export const renewToken = async (apiClient: AuthApi, token?: string) => {
try { try {
if (!token) { if (!token) {
throw new InfisicalSDKError("Unable to renew access token, no access token set. Are you sure you're authenticated?"); throw new InfisicalSDKError(
} "Unable to renew access token, no access token set."
);
}
const res = await apiClient.apiV1AuthTokenRenewPost({ const res = await apiClient.renewToken({ accessToken: token });
apiV1AuthTokenRenewPostRequest: { return res;
accessToken: token } catch (err) {
} throw newInfisicalError(err);
}); }
return res.data;
} catch (err) {
throw newInfisicalError(err);
}
}; };
export default class AuthClient { export default class AuthClient {
#sdkAuthenticator: AuthenticatorFunction; constructor(
#apiClient: InfisicalApi; private sdkAuthenticator: AuthenticatorFunction,
#accessToken?: string; private apiClient: AuthApi,
private _accessToken?: string
) {}
constructor(authenticator: AuthenticatorFunction, apiInstance: InfisicalApi, accessToken?: string) { awsIamAuth = {
this.#sdkAuthenticator = authenticator; login: async (options?: AwsAuthLoginOptions) => {
this.#apiClient = apiInstance; try {
this.#accessToken = accessToken; const identityId =
} options?.identityId || process.env[MACHINE_IDENTITY_ID_ENV_NAME];
if (!identityId) {
throw new InfisicalSDKError(
"Identity ID is required for AWS IAM authentication"
);
}
awsIamAuth = { const iamRequest = await performAwsIamLogin(await getAwsRegion());
login: async (options?: AwsAuthLoginOptions) => { const res = await this.apiClient.awsIamAuthLogin({
try { iamHttpRequestMethod: iamRequest.iamHttpRequestMethod,
const identityId = options?.identityId || process.env[MACHINE_IDENTITY_ID_ENV_NAME]; iamRequestBody: Buffer.from(iamRequest.iamRequestBody).toString(
"base64"
),
iamRequestHeaders: Buffer.from(
JSON.stringify(iamRequest.iamRequestHeaders)
).toString("base64"),
identityId,
});
if (!identityId) { return this.sdkAuthenticator(res.accessToken);
throw new InfisicalSDKError("Identity ID is required for AWS IAM authentication"); } catch (err) {
} throw newInfisicalError(err);
}
},
renew: async () => {
try {
const refreshedToken = await renewToken(
this.apiClient,
this._accessToken
);
return this.sdkAuthenticator(refreshedToken.accessToken);
} catch (err) {
throw newInfisicalError(err);
}
},
};
const iamRequest = await performAwsIamLogin(await getAwsRegion()); universalAuth = {
login: async (options: UniversalAuthLoginRequest) => {
try {
const res = await this.apiClient.universalAuthLogin(options);
return this.sdkAuthenticator(res.accessToken);
} catch (err) {
throw newInfisicalError(err);
}
},
renew: async () => {
try {
const refreshedToken = await renewToken(
this.apiClient,
this._accessToken
);
return this.sdkAuthenticator(refreshedToken.accessToken);
} catch (err) {
throw newInfisicalError(err);
}
},
};
const res = await this.#apiClient.apiV1AuthAwsAuthLoginPost({ accessToken = (token: string) => {
apiV1AuthAwsAuthLoginPostRequest: { return this.sdkAuthenticator(token);
iamHttpRequestMethod: iamRequest.iamHttpRequestMethod, };
iamRequestBody: Buffer.from(iamRequest.iamRequestBody).toString("base64"),
iamRequestHeaders: Buffer.from(JSON.stringify(iamRequest.iamRequestHeaders)).toString("base64"),
identityId
}
});
return this.#sdkAuthenticator(res.data.accessToken);
} catch (err) {
throw newInfisicalError(err);
}
},
renew: async () => {
try {
const refreshedToken = await renewToken(this.#apiClient, this.#accessToken);
return this.#sdkAuthenticator(refreshedToken.accessToken);
} catch (err) {
throw newInfisicalError(err);
}
}
};
universalAuth = {
login: async (options: ApiV1AuthUniversalAuthLoginPostRequest) => {
try {
const res = await this.#apiClient.apiV1AuthUniversalAuthLoginPost({
apiV1AuthUniversalAuthLoginPostRequest: options
});
return this.#sdkAuthenticator(res.data.accessToken);
} catch (err) {
throw newInfisicalError(err);
}
},
renew: async () => {
try {
const refreshedToken = await renewToken(this.#apiClient, this.#accessToken);
return this.#sdkAuthenticator(refreshedToken.accessToken);
} catch (err) {
throw newInfisicalError(err);
}
}
};
accessToken = (token: string) => {
return this.#sdkAuthenticator(token);
};
} }

View File

@@ -1,119 +1,64 @@
import { RawAxiosRequestConfig } from "axios"; import { DynamicSecretsApi } from "../api/endpoints/dynamic-secrets";
import { Configuration, DefaultApi as InfisicalApi } from "../infisicalapi_client"; import { TDynamicSecretProvider } from "./schemas/dynamic-secrets";
import type {
ApiV1DynamicSecretsGet200ResponseDynamicSecretsInner,
ApiV1DynamicSecretsLeasesLeaseIdDelete200Response,
ApiV1DynamicSecretsLeasesPost200Response,
DefaultApiApiV1DynamicSecretsLeasesLeaseIdDeleteRequest,
DefaultApiApiV1DynamicSecretsLeasesLeaseIdRenewPostRequest,
DefaultApiApiV1DynamicSecretsLeasesPostRequest,
DefaultApiApiV1DynamicSecretsNameDeleteRequest,
DefaultApiApiV1DynamicSecretsPostRequest
} from "../infisicalapi_client";
import type { TDynamicSecretProvider } from "./schemas/dynamic-secrets";
import { newInfisicalError } from "./errors"; import { newInfisicalError } from "./errors";
import {
export type CreateDynamicSecretOptions = Omit<DefaultApiApiV1DynamicSecretsPostRequest["apiV1DynamicSecretsPostRequest"], "provider"> & { CreateDynamicSecretOptions,
provider: TDynamicSecretProvider; DeleteDynamicSecretOptions,
}; CreateDynamicSecretLeaseOptions,
export type DeleteDynamicSecretOptions = DefaultApiApiV1DynamicSecretsNameDeleteRequest["apiV1DynamicSecretsNameDeleteRequest"]; DeleteDynamicSecretLeaseOptions,
export type CreateDynamicSecretLeaseOptions = DefaultApiApiV1DynamicSecretsLeasesPostRequest["apiV1DynamicSecretsLeasesPostRequest"]; RenewDynamicSecretLeaseOptions,
export type DeleteDynamicSecretLeaseOptions = } from "../api/types/dynamic-secrets";
DefaultApiApiV1DynamicSecretsLeasesLeaseIdDeleteRequest["apiV1DynamicSecretsLeasesLeaseIdDeleteRequest"];
export type RenewDynamicSecretLeaseOptions =
DefaultApiApiV1DynamicSecretsLeasesLeaseIdRenewPostRequest["apiV1DynamicSecretsLeasesLeaseIdRenewPostRequest"];
export type CreateDynamicSecretResult = ApiV1DynamicSecretsGet200ResponseDynamicSecretsInner;
export type DeleteDynamicSecretResult = ApiV1DynamicSecretsGet200ResponseDynamicSecretsInner;
export type CreateDynamicSecretLeaseResult = ApiV1DynamicSecretsLeasesPost200Response;
export type DeleteDynamicSecretLeaseResult = ApiV1DynamicSecretsLeasesLeaseIdDelete200Response;
export type RenewDynamicSecretLeaseResult = ApiV1DynamicSecretsLeasesLeaseIdDelete200Response;
export default class DynamicSecretsClient { export default class DynamicSecretsClient {
#apiInstance: InfisicalApi; constructor(private apiClient: DynamicSecretsApi) {}
#requestOptions: RawAxiosRequestConfig | undefined;
constructor(apiInstance: InfisicalApi, requestOptions: RawAxiosRequestConfig | undefined) {
this.#apiInstance = apiInstance;
this.#requestOptions = requestOptions;
}
async create(options: CreateDynamicSecretOptions): Promise<CreateDynamicSecretResult> { async create(options: CreateDynamicSecretOptions) {
try { try {
const res = await this.#apiInstance.apiV1DynamicSecretsPost( const res = await this.apiClient.create(options);
{ return res.dynamicSecret;
apiV1DynamicSecretsPostRequest: options as DefaultApiApiV1DynamicSecretsPostRequest["apiV1DynamicSecretsPostRequest"] } catch (err) {
}, throw newInfisicalError(err);
this.#requestOptions }
); }
return res.data.dynamicSecret; async delete(dynamicSecretName: string, options: DeleteDynamicSecretOptions) {
} catch (err) { try {
throw newInfisicalError(err); const res = await this.apiClient.delete(dynamicSecretName, options);
} return res.dynamicSecret;
} } catch (err) {
throw newInfisicalError(err);
}
}
async delete(dynamicSecretName: string, options: DeleteDynamicSecretOptions): Promise<DeleteDynamicSecretResult> { leases = {
try { create: async (options: CreateDynamicSecretLeaseOptions) => {
const res = await this.#apiInstance.apiV1DynamicSecretsNameDelete( try {
{ const res = await this.apiClient.leases.create(options);
name: dynamicSecretName, return res;
apiV1DynamicSecretsNameDeleteRequest: options } catch (err) {
}, throw newInfisicalError(err);
this.#requestOptions }
); },
return res.data.dynamicSecret; delete: async (
} catch (err) { leaseId: string,
throw newInfisicalError(err); options: DeleteDynamicSecretLeaseOptions
} ) => {
} try {
const res = await this.apiClient.leases.delete(leaseId, options);
return res;
} catch (err) {
throw newInfisicalError(err);
}
},
leases = { renew: async (leaseId: string, options: RenewDynamicSecretLeaseOptions) => {
create: async (options: CreateDynamicSecretLeaseOptions): Promise<CreateDynamicSecretLeaseResult> => { try {
try { const res = await this.apiClient.leases.renew(leaseId, options);
const res = await this.#apiInstance.apiV1DynamicSecretsLeasesPost( return res;
{ } catch (err) {
apiV1DynamicSecretsLeasesPostRequest: options throw newInfisicalError(err);
}, }
this.#requestOptions },
); };
return res.data;
} catch (err) {
throw newInfisicalError(err);
}
},
delete: async (leaseId: string, options: DeleteDynamicSecretLeaseOptions): Promise<DeleteDynamicSecretLeaseResult> => {
try {
const res = await this.#apiInstance.apiV1DynamicSecretsLeasesLeaseIdDelete(
{
leaseId: leaseId,
apiV1DynamicSecretsLeasesLeaseIdDeleteRequest: options
},
this.#requestOptions
);
return res.data;
} catch (err) {
throw newInfisicalError(err);
}
},
renew: async (leaseId: string, options: RenewDynamicSecretLeaseOptions): Promise<RenewDynamicSecretLeaseResult> => {
try {
const res = await this.#apiInstance.apiV1DynamicSecretsLeasesLeaseIdRenewPost(
{
leaseId: leaseId,
apiV1DynamicSecretsLeasesLeaseIdRenewPostRequest: options
},
this.#requestOptions
);
return res.data;
} catch (err) {
throw newInfisicalError(err);
}
}
};
} }

View File

@@ -1,33 +1,16 @@
import { RawAxiosRequestConfig } from "axios"; import { EnvironmentsApi } from "../api/endpoints/environments";
import { DefaultApi as InfisicalApi } from "../infisicalapi_client";
import type { ApiV1WorkspaceWorkspaceIdEnvironmentsPostRequest, ApiV1WorkspaceWorkspaceIdEnvironmentsPost200Response } from "../infisicalapi_client";
import { newInfisicalError } from "./errors"; import { newInfisicalError } from "./errors";
import { CreateEnvironmentOptions } from "../api/types/environments";
export type CreateEnvironmentOptions = {
projectId: string;
} & ApiV1WorkspaceWorkspaceIdEnvironmentsPostRequest;
export type CreateEnvironmentResult = ApiV1WorkspaceWorkspaceIdEnvironmentsPost200Response;
export default class EnvironmentsClient { export default class EnvironmentsClient {
#apiInstance: InfisicalApi; constructor(private apiClient: EnvironmentsApi) {}
#requestOptions: RawAxiosRequestConfig | undefined;
constructor(apiInstance: InfisicalApi, requestOptions: RawAxiosRequestConfig | undefined) {
this.#apiInstance = apiInstance;
this.#requestOptions = requestOptions;
}
create = async (options: CreateEnvironmentOptions): Promise<CreateEnvironmentResult["environment"]> => { create = async (options: CreateEnvironmentOptions) => {
try { try {
const res = await this.#apiInstance.apiV1WorkspaceWorkspaceIdEnvironmentsPost( const res = await this.apiClient.create(options);
{ return res.environment;
workspaceId: options.projectId, } catch (err) {
apiV1WorkspaceWorkspaceIdEnvironmentsPostRequest: options throw newInfisicalError(err);
}, }
this.#requestOptions };
);
return res.data.environment;
} catch (err) {
throw newInfisicalError(err);
}
};
} }

View File

@@ -1,57 +1,52 @@
import { AxiosError } from "axios"; import axios from "axios";
type TApiErrorResponse = {
statusCode: number;
message: string;
error: string;
};
export class InfisicalSDKError extends Error { export class InfisicalSDKError extends Error {
constructor(message: string) { constructor(message: string) {
super(message); super(message);
this.message = message; this.message = message;
this.name = "InfisicalSDKError"; this.name = "InfisicalSDKError";
} }
} }
export class InfisicalSDKRequestError extends Error { export class InfisicalSDKRequestError extends Error {
constructor( constructor(
message: string, message: string,
requestData: { requestData: {
url: string; url: string;
method: string; method: string;
statusCode: number; statusCode: number;
} }
) { ) {
super(message); super(message);
this.message = `[URL=${requestData.url}] [Method=${requestData.method}] [StatusCode=${requestData.statusCode}] ${message}`; this.message = `[URL=${requestData.url}] [Method=${requestData.method}] [StatusCode=${requestData.statusCode}] ${message}`;
this.name = "InfisicalSDKRequestError"; this.name = "InfisicalSDKRequestError";
} }
} }
export const newInfisicalError = (error: any) => { export const newInfisicalError = (error: any) => {
if (error instanceof AxiosError) { if (axios.isAxiosError(error)) {
const data = error?.response?.data as TApiErrorResponse; const data = error?.response?.data;
if (data?.message) { if (data?.message) {
let message = data.message; let message = data.message;
if (error.status === 422) { if (error.response?.status === 422) {
message = JSON.stringify(data); message = JSON.stringify(data);
} }
return new InfisicalSDKRequestError(message, { return new InfisicalSDKRequestError(message, {
url: error.response?.config.url || "", url: error.response?.config.url || "",
method: error.response?.config.method || "", method: error.response?.config.method || "",
statusCode: error.response?.status || 0 statusCode: error.response?.status || 0,
}); });
} else if (error.message) { } else if (error.message) {
return new InfisicalSDKError(error.message); return new InfisicalSDKError(error.message);
} else if (error.code) { } 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 // 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); return new InfisicalSDKError(error.code);
} else { } else {
return new InfisicalSDKError("Request failed with unknown error"); return new InfisicalSDKError("Request failed with unknown error");
} }
} }
return new InfisicalSDKError(error?.message || "An error occurred"); return new InfisicalSDKError(error?.message || "An error occurred");
}; };

View File

@@ -1,35 +1,37 @@
import { RawAxiosRequestConfig } from "axios"; import { FoldersApi } from "../api/endpoints/folders";
import { DefaultApi as InfisicalApi } from "../infisicalapi_client";
import type { ApiV1FoldersPostRequest, ApiV1FoldersPost200Response } from "../infisicalapi_client";
import { newInfisicalError } from "./errors"; import { newInfisicalError } from "./errors";
import { CreateFolderOptions, ListFoldersOptions } from "../api/types/folders";
export type CreateFolderOptions = {
projectId: string;
} & Omit<ApiV1FoldersPostRequest, "workspaceId" | "directory">;
export type CreateFolderResult = ApiV1FoldersPost200Response;
export default class FoldersClient { export default class FoldersClient {
#apiInstance: InfisicalApi; constructor(private apiClient: FoldersApi) {}
#requestOptions: RawAxiosRequestConfig | undefined;
constructor(apiInstance: InfisicalApi, requestOptions: RawAxiosRequestConfig | undefined) {
this.#apiInstance = apiInstance;
this.#requestOptions = requestOptions;
}
create = async (options: CreateFolderOptions): Promise<CreateFolderResult["folder"]> => { create = async (options: CreateFolderOptions) => {
try { try {
const res = await this.#apiInstance.apiV1FoldersPost( const res = await this.apiClient.create({
{ name: options.name,
apiV1FoldersPostRequest: { path: options.path,
...options, workspaceId: options.projectId,
workspaceId: options.projectId environment: options.environment,
} description: options.description,
}, });
this.#requestOptions return res.folder;
); } catch (err) {
return res.data.folder; throw newInfisicalError(err);
} catch (err) { }
throw newInfisicalError(err); };
}
}; listFolders = async (options: ListFoldersOptions) => {
try {
const res = await this.apiClient.listFolders({
environment: options.environment,
workspaceId: options.projectId,
path: options.path,
recursive: options.recursive,
lastSecretModified: options.lastSecretModified,
});
return res.folders;
} catch (err) {
throw newInfisicalError(err);
}
};
} }

View File

@@ -1,56 +1,29 @@
import { RawAxiosRequestConfig } from "axios"; import { ProjectsApi } from "../api/endpoints/projects";
import { DefaultApi as InfisicalApi } from "../infisicalapi_client";
import type {
ApiV2WorkspacePost200Response,
ApiV2WorkspacePostRequest,
ApiV2WorkspaceProjectIdMembershipsPost200Response,
ApiV2WorkspaceProjectIdMembershipsPostRequest
} from "../infisicalapi_client";
import { newInfisicalError } from "./errors"; import { newInfisicalError } from "./errors";
import { CreateProjectOptions, InviteMemberToProjectOptions } from "../api/types/projects";
export type CreateProjectOptions = ApiV2WorkspacePostRequest;
export type CreateProjectResult = ApiV2WorkspacePost200Response;
export type InviteMemberToProjectOptions = { projectId: string } & ApiV2WorkspaceProjectIdMembershipsPostRequest;
export type InviteMemberToProjectResult = ApiV2WorkspaceProjectIdMembershipsPost200Response;
export default class ProjectsClient { export default class ProjectsClient {
#apiInstance: InfisicalApi; constructor(private apiClient: ProjectsApi) {}
#requestOptions: RawAxiosRequestConfig | undefined;
constructor(apiInstance: InfisicalApi, requestOptions: RawAxiosRequestConfig | undefined) {
this.#apiInstance = apiInstance;
this.#requestOptions = requestOptions;
}
create = async (options: CreateProjectOptions): Promise<CreateProjectResult["project"]> => { create = async (options: CreateProjectOptions) => {
try { try {
const res = await this.#apiInstance.apiV2WorkspacePost( const res = await this.apiClient.create(options);
{ return res.project;
apiV2WorkspacePostRequest: options } catch (err) {
}, throw newInfisicalError(err);
this.#requestOptions }
); };
return res.data.project;
} catch (err) {
throw newInfisicalError(err);
}
};
inviteMembers = async (options: InviteMemberToProjectOptions): Promise<InviteMemberToProjectResult["memberships"]> => { inviteMembers = async (options: InviteMemberToProjectOptions) => {
try { try {
if (!options.usernames?.length && !options.emails?.length) { if (!options.usernames?.length && !options.emails?.length) {
throw new Error("Either usernames or emails must be provided"); throw new Error("Either usernames or emails must be provided");
} }
const res = await this.#apiInstance.apiV2WorkspaceProjectIdMembershipsPost( const res = await this.apiClient.inviteMembers(options);
{ return res.memberships;
projectId: options.projectId, } catch (err) {
apiV2WorkspaceProjectIdMembershipsPostRequest: options throw newInfisicalError(err);
}, }
this.#requestOptions };
);
return res.data.memberships;
} catch (err) {
throw newInfisicalError(err);
}
};
} }

View File

@@ -1,301 +1,394 @@
import { z } from "zod"; import { z } from "zod";
export enum SqlProviders { export enum SqlProviders {
Postgres = "postgres", Postgres = "postgres",
MySQL = "mysql2", MySQL = "mysql2",
Oracle = "oracledb", Oracle = "oracledb",
MsSQL = "mssql", MsSQL = "mssql",
SapAse = "sap-ase" SapAse = "sap-ase",
} }
export enum ElasticSearchAuthTypes { export enum ElasticSearchAuthTypes {
User = "user", User = "user",
ApiKey = "api-key" ApiKey = "api-key",
} }
export enum LdapCredentialType { export enum LdapCredentialType {
Dynamic = "dynamic", Dynamic = "dynamic",
Static = "static" Static = "static",
} }
export enum TotpConfigType { export enum TotpConfigType {
URL = "url", URL = "url",
MANUAL = "manual" MANUAL = "manual",
} }
export enum TotpAlgorithm { export enum TotpAlgorithm {
SHA1 = "sha1", SHA1 = "sha1",
SHA256 = "sha256", SHA256 = "sha256",
SHA512 = "sha512" 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({ const DynamicSecretRedisDBSchema = z.object({
host: z.string().trim().toLowerCase(), host: z.string().trim().toLowerCase(),
port: z.number(), port: z.number(),
username: z.string().trim(), // this is often "default". username: z.string().trim(), // this is often "default".
password: z.string().trim().optional(), password: z.string().trim().optional(),
creationStatement: z.string().trim(), creationStatement: z.string().trim(),
revocationStatement: z.string().trim(), revocationStatement: z.string().trim(),
renewStatement: z.string().trim().optional(), renewStatement: z.string().trim().optional(),
ca: z.string().optional() ca: z.string().optional(),
}); });
const DynamicSecretAwsElastiCacheSchema = z.object({ const DynamicSecretAwsElastiCacheSchema = z.object({
clusterName: z.string().trim().min(1), clusterName: z.string().trim().min(1),
accessKeyId: z.string().trim().min(1), accessKeyId: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1), secretAccessKey: z.string().trim().min(1),
region: z.string().trim(), region: z.string().trim(),
creationStatement: z.string().trim(), creationStatement: z.string().trim(),
revocationStatement: z.string().trim(), revocationStatement: z.string().trim(),
ca: z.string().optional() ca: z.string().optional(),
}); });
const DynamicSecretElasticSearchSchema = z.object({ const DynamicSecretElasticSearchSchema = z.object({
host: z.string().trim().min(1), host: z.string().trim().min(1),
port: z.number(), port: z.number(),
roles: z.array(z.string().trim().min(1)).min(1), roles: z.array(z.string().trim().min(1)).min(1),
// two auth types "user, apikey" // two auth types "user, apikey"
auth: z.discriminatedUnion("type", [ auth: z.discriminatedUnion("type", [
z.object({ z.object({
type: z.literal(ElasticSearchAuthTypes.User), type: z.literal(ElasticSearchAuthTypes.User),
username: z.string().trim(), username: z.string().trim(),
password: z.string().trim() password: z.string().trim(),
}), }),
z.object({ z.object({
type: z.literal(ElasticSearchAuthTypes.ApiKey), type: z.literal(ElasticSearchAuthTypes.ApiKey),
apiKey: z.string().trim(), apiKey: z.string().trim(),
apiKeyId: z.string().trim() apiKeyId: z.string().trim(),
}) }),
]), ]),
ca: z.string().optional() ca: z.string().optional(),
}); });
const DynamicSecretRabbitMqSchema = z.object({ const DynamicSecretRabbitMqSchema = z.object({
host: z.string().trim().min(1), host: z.string().trim().min(1),
port: z.number(), port: z.number(),
tags: z.array(z.string().trim()).default([]), tags: z.array(z.string().trim()).default([]),
username: z.string().trim().min(1), username: z.string().trim().min(1),
password: z.string().trim().min(1), password: z.string().trim().min(1),
ca: z.string().optional(), ca: z.string().optional(),
virtualHost: z.object({ virtualHost: z.object({
name: z.string().trim().min(1), name: z.string().trim().min(1),
permissions: z.object({ permissions: z.object({
read: z.string().trim().min(1), read: z.string().trim().min(1),
write: z.string().trim().min(1), write: z.string().trim().min(1),
configure: z.string().trim().min(1) configure: z.string().trim().min(1),
}) }),
}) }),
}); });
const DynamicSecretSqlDBSchema = z.object({ const DynamicSecretSqlDBSchema = z.object({
client: z.nativeEnum(SqlProviders), client: z.nativeEnum(SqlProviders),
host: z.string().trim().toLowerCase(), host: z.string().trim().toLowerCase(),
port: z.number(), port: z.number(),
database: z.string().trim(), database: z.string().trim(),
username: z.string().trim(), username: z.string().trim(),
password: z.string().trim(), password: z.string().trim(),
creationStatement: z.string().trim(), creationStatement: z.string().trim(),
revocationStatement: z.string().trim(), revocationStatement: z.string().trim(),
renewStatement: z.string().trim().optional(), renewStatement: z.string().trim().optional(),
ca: z.string().optional() ca: z.string().optional(),
passwordRequirements: passwordRequirementsSchema.optional(),
}); });
const DynamicSecretCassandraSchema = z.object({ const DynamicSecretCassandraSchema = z.object({
host: z.string().trim().toLowerCase(), host: z.string().trim().toLowerCase(),
port: z.number(), port: z.number(),
localDataCenter: z.string().trim().min(1), localDataCenter: z.string().trim().min(1),
keyspace: z.string().trim().optional(), keyspace: z.string().trim().optional(),
username: z.string().trim(), username: z.string().trim(),
password: z.string().trim(), password: z.string().trim(),
creationStatement: z.string().trim(), creationStatement: z.string().trim(),
revocationStatement: z.string().trim(), revocationStatement: z.string().trim(),
renewStatement: z.string().trim().optional(), renewStatement: z.string().trim().optional(),
ca: z.string().optional() ca: z.string().optional(),
}); });
const DynamicSecretSapAseSchema = z.object({ const DynamicSecretSapAseSchema = z.object({
host: z.string().trim().toLowerCase(), host: z.string().trim().toLowerCase(),
port: z.number(), port: z.number(),
database: z.string().trim(), database: z.string().trim(),
username: z.string().trim(), username: z.string().trim(),
password: z.string().trim(), password: z.string().trim(),
creationStatement: z.string().trim(), creationStatement: z.string().trim(),
revocationStatement: z.string().trim() revocationStatement: z.string().trim(),
}); });
const DynamicSecretAwsIamSchema = z.object({ const DynamicSecretAwsIamSchema = z.object({
accessKey: z.string().trim().min(1), accessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1), secretAccessKey: z.string().trim().min(1),
region: z.string().trim().min(1), region: z.string().trim().min(1),
awsPath: z.string().trim().optional(), awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(), permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(), policyDocument: z.string().trim().optional(),
userGroups: z.string().trim().optional(), userGroups: z.string().trim().optional(),
policyArns: z.string().trim().optional() policyArns: z.string().trim().optional(),
}); });
const DynamicSecretMongoAtlasSchema = z.object({ const DynamicSecretMongoAtlasSchema = z.object({
adminPublicKey: z.string().trim().min(1).describe("Admin user public api key"), adminPublicKey: z
adminPrivateKey: z.string().trim().min(1).describe("Admin user private api key"), .string()
groupId: z.string().trim().min(1).describe("Unique 24-hexadecimal digit string that identifies your project. This is same as project id"), .trim()
roles: z .min(1)
.object({ .describe("Admin user public api key"),
collectionName: z.string().optional().describe("Collection on which this role applies."), adminPrivateKey: z
databaseName: z.string().min(1).describe("Database to which the user is granted access privileges."), .string()
roleName: z .trim()
.string() .min(1)
.min(1) .describe("Admin user private api key"),
.describe( groupId: z
' Enum: "atlasAdmin" "backup" "clusterMonitor" "dbAdmin" "dbAdminAnyDatabase" "enableSharding" "read" "readAnyDatabase" "readWrite" "readWriteAnyDatabase" "<a custom role name>".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.' .string()
) .trim()
}) .min(1)
.array() .describe(
.min(1), "Unique 24-hexadecimal digit string that identifies your project. This is same as project id"
scopes: z ),
.object({ roles: z
name: z .object({
.string() collectionName: z
.min(1) .string()
.describe("Human-readable label that identifies the cluster or MongoDB Atlas Data Lake that this database user can access."), .optional()
type: z.string().min(1).describe("Category of resource that this database user can access. Enum: CLUSTER, DATA_LAKE, STREAM") .describe("Collection on which this role applies."),
}) databaseName: z
.array() .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" "<a custom role name>".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({ const DynamicSecretMongoDBSchema = z.object({
host: z.string().min(1).trim().toLowerCase(), host: z.string().min(1).trim().toLowerCase(),
port: z.number().optional(), port: z.number().optional(),
username: z.string().min(1).trim(), username: z.string().min(1).trim(),
password: z.string().min(1).trim(), password: z.string().min(1).trim(),
database: z.string().min(1).trim(), database: z.string().min(1).trim(),
ca: z.string().min(1).optional(), ca: z.string().min(1).optional(),
roles: z roles: z
.string() .string()
.array() .array()
.min(1) .min(1)
.describe( .describe(
'Enum: "atlasAdmin" "backup" "clusterMonitor" "dbAdmin" "dbAdminAnyDatabase" "enableSharding" "read" "readAnyDatabase" "readWrite" "readWriteAnyDatabase" "<a custom role name>".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.' 'Enum: "atlasAdmin" "backup" "clusterMonitor" "dbAdmin" "dbAdminAnyDatabase" "enableSharding" "read" "readAnyDatabase" "readWrite" "readWriteAnyDatabase" "<a custom role name>".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({ const DynamicSecretSapHanaSchema = z.object({
host: z.string().trim().toLowerCase(), host: z.string().trim().toLowerCase(),
port: z.number(), port: z.number(),
username: z.string().trim(), username: z.string().trim(),
password: z.string().trim(), password: z.string().trim(),
creationStatement: z.string().trim(), creationStatement: z.string().trim(),
revocationStatement: z.string().trim(), revocationStatement: z.string().trim(),
renewStatement: z.string().trim().optional(), renewStatement: z.string().trim().optional(),
ca: z.string().optional() ca: z.string().optional(),
}); });
const DynamicSecretSnowflakeSchema = z.object({ const DynamicSecretSnowflakeSchema = z.object({
accountId: z.string().trim().min(1), accountId: z.string().trim().min(1),
orgId: z.string().trim().min(1), orgId: z.string().trim().min(1),
username: z.string().trim().min(1), username: z.string().trim().min(1),
password: z.string().trim().min(1), password: z.string().trim().min(1),
creationStatement: z.string().trim().min(1), creationStatement: z.string().trim().min(1),
revocationStatement: z.string().trim().min(1), revocationStatement: z.string().trim().min(1),
renewStatement: z.string().trim().optional() renewStatement: z.string().trim().optional(),
}); });
const AzureEntraIDSchema = z.object({ const AzureEntraIDSchema = z.object({
tenantId: z.string().trim().min(1), tenantId: z.string().trim().min(1),
userId: z.string().trim().min(1), userId: z.string().trim().min(1),
email: z.string().trim().min(1), email: z.string().trim().min(1),
applicationId: z.string().trim().min(1), applicationId: z.string().trim().min(1),
clientSecret: z.string().trim().min(1) clientSecret: z.string().trim().min(1),
}); });
const LdapSchema = z.union([ const LdapSchema = z.union([
z.object({ z.object({
url: z.string().trim().min(1), url: z.string().trim().min(1),
binddn: z.string().trim().min(1), binddn: z.string().trim().min(1),
bindpass: z.string().trim().min(1), bindpass: z.string().trim().min(1),
ca: z.string().optional(), ca: z.string().optional(),
credentialType: z.literal(LdapCredentialType.Dynamic).optional().default(LdapCredentialType.Dynamic), credentialType: z
creationLdif: z.string().min(1), .literal(LdapCredentialType.Dynamic)
revocationLdif: z.string().min(1), .optional()
rollbackLdif: z.string().optional() .default(LdapCredentialType.Dynamic),
}), creationLdif: z.string().min(1),
z.object({ revocationLdif: z.string().min(1),
url: z.string().trim().min(1), rollbackLdif: z.string().optional(),
binddn: z.string().trim().min(1), }),
bindpass: z.string().trim().min(1), z.object({
ca: z.string().optional(), url: z.string().trim().min(1),
credentialType: z.literal(LdapCredentialType.Static), binddn: z.string().trim().min(1),
rotationLdif: z.string().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", [ const DynamicSecretTotpSchema = z.discriminatedUnion("configType", [
z.object({ z.object({
configType: z.literal(TotpConfigType.URL), configType: z.literal(TotpConfigType.URL),
url: z url: z
.string() .string()
.url() .url()
.trim() .trim()
.min(1) .min(1)
.refine(val => { .refine((val) => {
const urlObj = new URL(val); const urlObj = new URL(val);
const secret = urlObj.searchParams.get("secret"); const secret = urlObj.searchParams.get("secret");
return Boolean(secret); return Boolean(secret);
}, "OTP URL must contain secret field") }, "OTP URL must contain secret field"),
}), }),
z.object({ z.object({
configType: z.literal(TotpConfigType.MANUAL), configType: z.literal(TotpConfigType.MANUAL),
secret: z secret: z
.string() .string()
.trim() .trim()
.min(1) .min(1)
.transform(val => val.replace(/\s+/g, "")), .transform((val) => val.replace(/\s+/g, "")),
period: z.number().optional(), period: z.number().optional(),
algorithm: z.nativeEnum(TotpAlgorithm).optional(), algorithm: z.nativeEnum(TotpAlgorithm).optional(),
digits: z.number().optional() digits: z.number().optional(),
}) }),
]); ]);
export enum DynamicSecretProviders { export enum DynamicSecretProviders {
SqlDatabase = "sql-database", SqlDatabase = "sql-database",
Cassandra = "cassandra", Cassandra = "cassandra",
AwsIam = "aws-iam", AwsIam = "aws-iam",
Redis = "redis", Redis = "redis",
AwsElastiCache = "aws-elasticache", AwsElastiCache = "aws-elasticache",
MongoAtlas = "mongo-db-atlas", MongoAtlas = "mongo-db-atlas",
ElasticSearch = "elastic-search", ElasticSearch = "elastic-search",
MongoDB = "mongo-db", MongoDB = "mongo-db",
RabbitMq = "rabbit-mq", RabbitMq = "rabbit-mq",
AzureEntraID = "azure-entra-id", AzureEntraID = "azure-entra-id",
Ldap = "ldap", Ldap = "ldap",
SapHana = "sap-hana", SapHana = "sap-hana",
Snowflake = "snowflake", Snowflake = "snowflake",
Totp = "totp", Totp = "totp",
SapAse = "sap-ase" SapAse = "sap-ase",
} }
const DynamicSecretProviderSchema = z.discriminatedUnion("type", [ const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema }), z.object({
z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }), type: z.literal(DynamicSecretProviders.SqlDatabase),
z.object({ type: z.literal(DynamicSecretProviders.SapAse), inputs: DynamicSecretSapAseSchema }), inputs: DynamicSecretSqlDBSchema,
z.object({ type: z.literal(DynamicSecretProviders.AwsIam), inputs: DynamicSecretAwsIamSchema }), }),
z.object({ type: z.literal(DynamicSecretProviders.Redis), inputs: DynamicSecretRedisDBSchema }), z.object({
z.object({ type: z.literal(DynamicSecretProviders.SapHana), inputs: DynamicSecretSapHanaSchema }), type: z.literal(DynamicSecretProviders.Cassandra),
z.object({ type: z.literal(DynamicSecretProviders.AwsElastiCache), inputs: DynamicSecretAwsElastiCacheSchema }), inputs: DynamicSecretCassandraSchema,
z.object({ type: z.literal(DynamicSecretProviders.MongoAtlas), inputs: DynamicSecretMongoAtlasSchema }), }),
z.object({ type: z.literal(DynamicSecretProviders.ElasticSearch), inputs: DynamicSecretElasticSearchSchema }), z.object({
z.object({ type: z.literal(DynamicSecretProviders.MongoDB), inputs: DynamicSecretMongoDBSchema }), type: z.literal(DynamicSecretProviders.SapAse),
z.object({ type: z.literal(DynamicSecretProviders.RabbitMq), inputs: DynamicSecretRabbitMqSchema }), inputs: DynamicSecretSapAseSchema,
z.object({ type: z.literal(DynamicSecretProviders.AzureEntraID), inputs: AzureEntraIDSchema }), }),
z.object({ type: z.literal(DynamicSecretProviders.Ldap), inputs: LdapSchema }), z.object({
z.object({ type: z.literal(DynamicSecretProviders.Snowflake), inputs: DynamicSecretSnowflakeSchema }), type: z.literal(DynamicSecretProviders.AwsIam),
z.object({ type: z.literal(DynamicSecretProviders.Totp), inputs: DynamicSecretTotpSchema }) 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<typeof DynamicSecretProviderSchema>; export type TDynamicSecretProvider = z.infer<
typeof DynamicSecretProviderSchema
>;

View File

@@ -1,215 +1,142 @@
import { RawAxiosRequestConfig } from "axios"; import { SecretsApi } from "../api/endpoints/secrets";
import { DefaultApi as InfisicalApi } from "../infisicalapi_client";
import type {
ApiV3SecretsRawGet200Response,
ApiV3SecretsRawSecretNameGet200Response,
ApiV3SecretsRawSecretNamePost200Response,
DefaultApiApiV3SecretsRawSecretNameDeleteRequest,
DefaultApiApiV3SecretsRawSecretNamePatchRequest,
DefaultApiApiV3SecretsRawSecretNamePostRequest
} from "../infisicalapi_client";
import { newInfisicalError } from "./errors"; import { newInfisicalError } from "./errors";
import { getUniqueSecretsByKey } from "./util"; import { ListSecretsOptions, GetSecretOptions, UpdateSecretOptions, CreateSecretOptions, DeleteSecretOptions } from "../api/types/secrets";
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;
};
export type UpdateSecretOptions = Omit<DefaultApiApiV3SecretsRawSecretNamePatchRequest["apiV3SecretsRawSecretNamePatchRequest"], "workspaceId"> & {
projectId: string;
};
export type CreateSecretOptions = Omit<DefaultApiApiV3SecretsRawSecretNamePostRequest["apiV3SecretsRawSecretNamePostRequest"], "workspaceId"> & {
projectId: string;
};
export type DeleteSecretOptions = Omit<DefaultApiApiV3SecretsRawSecretNameDeleteRequest["apiV3SecretsRawSecretNameDeleteRequest"], "workspaceId"> & {
projectId: string;
};
export type ListSecretsResult = ApiV3SecretsRawGet200Response;
export type GetSecretResult = ApiV3SecretsRawSecretNameGet200Response["secret"];
export type UpdateSecretResult = ApiV3SecretsRawSecretNamePost200Response;
export type CreateSecretResult = ApiV3SecretsRawSecretNamePost200Response;
export type DeleteSecretResult = ApiV3SecretsRawSecretNamePost200Response;
const convertBool = (value?: boolean) => (value ? "true" : "false"); const convertBool = (value?: boolean) => (value ? "true" : "false");
const defaultBoolean = (value?: boolean, defaultValue: boolean = false) => { const defaultBoolean = (value?: boolean, defaultValue: boolean = false) => {
if (value === undefined) { if (value === undefined) {
return defaultValue; return defaultValue;
} }
return value; return value;
}; };
export default class SecretsClient { export default class SecretsClient {
#apiInstance: InfisicalApi; constructor(private apiClient: SecretsApi) {}
#requestOptions: RawAxiosRequestConfig | undefined;
constructor(apiInstance: InfisicalApi, requestOptions: RawAxiosRequestConfig | undefined) {
this.#apiInstance = apiInstance;
this.#requestOptions = requestOptions;
}
listSecrets = async (options: ListSecretsOptions): Promise<ListSecretsResult> => { listSecrets = async (options: ListSecretsOptions) => {
try { try {
const res = await this.#apiInstance.apiV3SecretsRawGet( return await this.apiClient.listSecrets({
{ workspaceId: options.projectId,
viewSecretValue: convertBool(options.viewSecretValue ?? true), environment: options.environment,
environment: options.environment, expandSecretReferences: convertBool(
workspaceId: options.projectId, defaultBoolean(options.expandSecretReferences, true)
expandSecretReferences: convertBool(defaultBoolean(options.expandSecretReferences, true)), ),
includeImports: convertBool(options.includeImports), includeImports: convertBool(options.includeImports),
recursive: convertBool(options.recursive), recursive: convertBool(options.recursive),
secretPath: options.secretPath, secretPath: options.secretPath,
tagSlugs: options.tagSlugs ? options.tagSlugs.join(",") : undefined tagSlugs: options.tagSlugs ? options.tagSlugs.join(",") : undefined,
}, viewSecretValue: convertBool(options.viewSecretValue ?? true),
this.#requestOptions });
); } catch (err) {
return res.data; throw newInfisicalError(err);
} catch (err) { }
throw newInfisicalError(err); };
}
};
listSecretsWithImports = async (options: Omit<ListSecretsOptions, "includeImports">): Promise<ListSecretsResult["secrets"]> => { listSecretsWithImports = async (
const res = await this.listSecrets({ options: Omit<ListSecretsOptions, "includeImports">
...options, ) => {
includeImports: true const res = await this.listSecrets({
}); ...options,
includeImports: true,
});
let { imports, secrets } = res; let { imports, secrets } = res;
if (imports) { if (imports) {
if (options.recursive) { for (const imp of imports) {
secrets = getUniqueSecretsByKey(secrets); for (const importedSecret of imp.secrets) {
} // CASE: We need to ensure that the imported values don't override the "base" secrets.
// Priority order is:
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) // Local/Preset variables -> Actual secrets -> Imported secrets (high->low)
// Check if the secret already exists in the secrets list // Check if the secret already exists in the secrets list
if (!secrets.find(s => s.secretKey === importedSecret.secretKey)) { if (!secrets.find((s) => s.secretKey === importedSecret.secretKey)) {
secrets.push({ secrets.push({
...importedSecret, ...importedSecret,
secretPath: imp.secretPath, secretPath: imp.secretPath,
// These fields are not returned by the API createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
createdAt: new Date().toISOString(), tags: [],
tags: [] });
}); }
} }
} }
} }
}
return secrets; return secrets;
}; };
getSecret = async (options: GetSecretOptions): Promise<GetSecretResult> => { getSecret = async (options: GetSecretOptions) => {
try { try {
const res = await this.#apiInstance.apiV3SecretsRawSecretNameGet( const res = await this.apiClient.getSecret({
{ secretName: options.secretName,
viewSecretValue: convertBool(options.viewSecretValue ?? true), workspaceId: options.projectId,
environment: options.environment, environment: options.environment,
secretName: options.secretName, expandSecretReferences: convertBool(
workspaceId: options.projectId, defaultBoolean(options.expandSecretReferences, true)
expandSecretReferences: convertBool(defaultBoolean(options.expandSecretReferences, true)), ),
includeImports: convertBool(options.includeImports), includeImports: convertBool(options.includeImports),
secretPath: options.secretPath, secretPath: options.secretPath,
type: options.type, type: options.type,
version: options.version version: options.version,
}, viewSecretValue: convertBool(options.viewSecretValue ?? true),
this.#requestOptions });
); return res.secret;
return res.data.secret; } catch (err) {
} catch (err) { throw newInfisicalError(err);
throw newInfisicalError(err); }
} };
};
updateSecret = async ( updateSecret = async (secretName: string, options: UpdateSecretOptions) => {
secretName: DefaultApiApiV3SecretsRawSecretNamePatchRequest["secretName"], try {
options: UpdateSecretOptions return await this.apiClient.updateSecret(secretName, {
): Promise<UpdateSecretResult> => { workspaceId: options.projectId,
try { environment: options.environment,
const res = await this.#apiInstance.apiV3SecretsRawSecretNamePatch( secretValue: options.secretValue,
{ newSecretName: options.newSecretName,
secretName, secretComment: options.secretComment,
apiV3SecretsRawSecretNamePatchRequest: { secretPath: options.secretPath,
...options, secretReminderNote: options.secretReminderNote,
workspaceId: options.projectId secretReminderRepeatDays: options.secretReminderRepeatDays,
} skipMultilineEncoding: options.skipMultilineEncoding,
}, tagIds: options.tagIds,
this.#requestOptions type: options.type,
); metadata: options.metadata,
return res.data; });
} catch (err) { } catch (err) {
throw newInfisicalError(err); throw newInfisicalError(err);
} }
}; };
createSecret = async ( createSecret = async (secretName: string, options: CreateSecretOptions) => {
secretName: DefaultApiApiV3SecretsRawSecretNamePostRequest["secretName"], try {
options: CreateSecretOptions return await this.apiClient.createSecret(secretName, {
): Promise<CreateSecretResult> => { workspaceId: options.projectId,
try { environment: options.environment,
const res = await this.#apiInstance.apiV3SecretsRawSecretNamePost( secretValue: options.secretValue,
{ secretComment: options.secretComment,
secretName, secretPath: options.secretPath,
apiV3SecretsRawSecretNamePostRequest: { secretReminderNote: options.secretReminderNote,
...options, secretReminderRepeatDays: options.secretReminderRepeatDays,
workspaceId: options.projectId skipMultilineEncoding: options.skipMultilineEncoding,
} tagIds: options.tagIds,
}, type: options.type,
this.#requestOptions });
); } catch (err) {
return res.data; throw newInfisicalError(err);
} catch (err) { }
throw newInfisicalError(err); };
}
};
deleteSecret = async ( deleteSecret = async (secretName: string, options: DeleteSecretOptions) => {
secretName: DefaultApiApiV3SecretsRawSecretNameDeleteRequest["secretName"], try {
options: DeleteSecretOptions return await this.apiClient.deleteSecret(secretName, {
): Promise<DeleteSecretResult> => { workspaceId: options.projectId,
try { environment: options.environment,
const res = await this.#apiInstance.apiV3SecretsRawSecretNameDelete( secretPath: options.secretPath,
{ type: options.type,
secretName, });
apiV3SecretsRawSecretNameDeleteRequest: { } catch (err) {
...options, throw newInfisicalError(err);
workspaceId: options.projectId }
} };
},
this.#requestOptions
);
return res.data;
} catch (err) {
throw newInfisicalError(err);
}
};
} }

View File

@@ -2,9 +2,7 @@ import axios from "axios";
import { AWS_IDENTITY_DOCUMENT_URI, AWS_TOKEN_METADATA_URI } from "./constants"; import { AWS_IDENTITY_DOCUMENT_URI, AWS_TOKEN_METADATA_URI } from "./constants";
import AWS from "aws-sdk"; import AWS from "aws-sdk";
import { InfisicalSDKError } from "./errors"; import { InfisicalSDKError } from "./errors";
import { ApiV3SecretsRawGet200Response } from "../infisicalapi_client"; import { Secret } from "../api/types";
type Secret = ApiV3SecretsRawGet200Response["secrets"][number];
export const getUniqueSecretsByKey = (secrets: Secret[]) => { export const getUniqueSecretsByKey = (secrets: Secret[]) => {
const secretMap = new Map<string, Secret>(); const secretMap = new Map<string, Secret>();

View File

@@ -1,141 +1,100 @@
import { Configuration, DefaultApi as InfisicalApi } from "./infisicalapi_client"; import { ApiClient } from "./api/base";
import { DefaultApiApiV1DynamicSecretsLeasesPostRequest } from "./infisicalapi_client"; import { AuthApi } from "./api/endpoints/auth";
import { SecretsApi } from "./api/endpoints/secrets";
import { DynamicSecretsApi } from "./api/endpoints/dynamic-secrets";
import { EnvironmentsApi } from "./api/endpoints/environments";
import { ProjectsApi } from "./api/endpoints/projects";
import { FoldersApi } from "./api/endpoints/folders";
import SecretsClient from "./custom/secrets"; import SecretsClient from "./custom/secrets";
import AuthClient from "./custom/auth"; import AuthClient from "./custom/auth";
import axios, { AxiosError, AxiosInstance, RawAxiosRequestConfig } from "axios";
import DynamicSecretsClient from "./custom/dynamic-secrets"; import DynamicSecretsClient from "./custom/dynamic-secrets";
import * as ApiClient from "./infisicalapi_client";
import EnvironmentsClient from "./custom/environments"; import EnvironmentsClient from "./custom/environments";
import ProjectsClient from "./custom/projects"; import ProjectsClient from "./custom/projects";
import FoldersClient from "./custom/folders"; import FoldersClient from "./custom/folders";
declare module "axios" {
interface AxiosRequestConfig {
_retryCount?: number;
}
}
const buildRestClient = (apiClient: InfisicalApi, requestOptions?: RawAxiosRequestConfig) => {
return {
// Add more as we go
apiV1DynamicSecretsLeasesPost: (options: DefaultApiApiV1DynamicSecretsLeasesPostRequest) =>
apiClient.apiV1DynamicSecretsLeasesPost(options, requestOptions)
};
};
const setupAxiosRetry = () => {
const axiosInstance = axios.create();
const maxRetries = 4;
const initialRetryDelay = 1000;
const backoffFactor = 2;
axiosInstance.interceptors.response.use(null, (error: AxiosError) => {
const config = error?.config;
if (!config) {
return Promise.reject(error);
}
if (!config._retryCount) config._retryCount = 0;
// handle rate limits and network errors
if ((error.response?.status === 429 || error.response?.status === undefined) && config && config._retryCount! < maxRetries) {
config._retryCount!++;
const baseDelay = initialRetryDelay * Math.pow(backoffFactor, config._retryCount! - 1);
const jitter = baseDelay * 0.2; // 20% +/- jitter
const exponentialDelay = baseDelay + (Math.random() * 2 - 1) * jitter;
return new Promise(resolve => {
setTimeout(() => {
resolve(axiosInstance(config));
}, exponentialDelay);
});
}
return Promise.reject(error);
});
return axiosInstance;
};
// We need to do bind(this) because the authenticate method is a private method, and usually you can't call private methods from outside the class.
type InfisicalSDKOptions = { type InfisicalSDKOptions = {
siteUrl?: string; siteUrl?: string;
}; };
class InfisicalSDK { class InfisicalSDK {
#apiInstance: InfisicalApi; private apiClient: ApiClient;
#requestOptions: RawAxiosRequestConfig | undefined; // API instances
#secretsClient: SecretsClient; private authApi: AuthApi;
#dynamicSecretsClient: DynamicSecretsClient; private secretsApi: SecretsApi;
#environmentsClient: EnvironmentsClient; private dynamicSecretsApi: DynamicSecretsApi;
#projectsClient: ProjectsClient; private environmentsApi: EnvironmentsApi;
#foldersClient: FoldersClient; private projectsApi: ProjectsApi;
#authClient: AuthClient; private foldersApi: FoldersApi;
#basePath: string;
axiosInstance: AxiosInstance;
constructor(options?: InfisicalSDKOptions) { // Domain clients
this.#basePath = options?.siteUrl || "https://app.infisical.com"; private authClient: AuthClient;
this.axiosInstance = setupAxiosRetry(); private secretsClient: SecretsClient;
private dynamicSecretsClient: DynamicSecretsClient;
private environmentsClient: EnvironmentsClient;
private projectsClient: ProjectsClient;
private foldersClient: FoldersClient;
this.#apiInstance = new InfisicalApi( constructor(options?: InfisicalSDKOptions) {
new Configuration({ const baseURL = options?.siteUrl || "https://app.infisical.com";
basePath: this.#basePath
}),
undefined,
this.axiosInstance
);
this.#authClient = new AuthClient(this.authenticate.bind(this), this.#apiInstance); // Initialize the base API client
this.#dynamicSecretsClient = new DynamicSecretsClient(this.#apiInstance, this.#requestOptions); this.apiClient = new ApiClient({ baseURL });
this.#secretsClient = new SecretsClient(this.#apiInstance, this.#requestOptions);
this.#environmentsClient = new EnvironmentsClient(this.#apiInstance, this.#requestOptions);
this.#projectsClient = new ProjectsClient(this.#apiInstance, this.#requestOptions);
this.#foldersClient = new FoldersClient(this.#apiInstance, this.#requestOptions);
this.rest = () => buildRestClient(this.#apiInstance, this.#requestOptions);
}
private authenticate(accessToken: string) { // Initialize API service instances
this.#apiInstance = new InfisicalApi( this.authApi = new AuthApi(this.apiClient);
new Configuration({ this.secretsApi = new SecretsApi(this.apiClient);
basePath: this.#basePath, this.dynamicSecretsApi = new DynamicSecretsApi(this.apiClient);
accessToken this.environmentsApi = new EnvironmentsApi(this.apiClient);
}), this.projectsApi = new ProjectsApi(this.apiClient);
undefined, this.foldersApi = new FoldersApi(this.apiClient);
this.axiosInstance
);
this.#requestOptions = { // Initialize domain clients
headers: { this.authClient = new AuthClient(
Authorization: `Bearer ${accessToken}` this.authenticate.bind(this),
} this.authApi
}; );
this.secretsClient = new SecretsClient(this.secretsApi);
this.dynamicSecretsClient = new DynamicSecretsClient(
this.dynamicSecretsApi
);
this.environmentsClient = new EnvironmentsClient(this.environmentsApi);
this.projectsClient = new ProjectsClient(this.projectsApi);
this.foldersClient = new FoldersClient(this.foldersApi);
}
this.rest = () => buildRestClient(this.#apiInstance, this.#requestOptions); private authenticate(accessToken: string) {
this.#secretsClient = new SecretsClient(this.#apiInstance, this.#requestOptions); // Set the token on the API client
this.#dynamicSecretsClient = new DynamicSecretsClient(this.#apiInstance, this.#requestOptions); this.apiClient.setAccessToken(accessToken);
this.#authClient = new AuthClient(this.authenticate.bind(this), this.#apiInstance, accessToken);
this.#environmentsClient = new EnvironmentsClient(this.#apiInstance, this.#requestOptions);
this.#projectsClient = new ProjectsClient(this.#apiInstance, this.#requestOptions);
this.#foldersClient = new FoldersClient(this.#apiInstance, this.#requestOptions);
return this; // Reinitialize the auth client with the token
} this.authClient = new AuthClient(
this.authenticate.bind(this),
this.authApi,
accessToken
);
secrets = () => this.#secretsClient; return this;
environments = () => this.#environmentsClient; }
projects = () => this.#projectsClient;
folders = () => this.#foldersClient; // Public methods to access domain clients
dynamicSecrets = () => this.#dynamicSecretsClient; secrets = () => this.secretsClient;
auth = () => this.#authClient; environments = () => this.environmentsClient;
rest = () => buildRestClient(this.#apiInstance, this.#requestOptions); projects = () => this.projectsClient;
folders = () => this.foldersClient;
dynamicSecrets = () => this.dynamicSecretsClient;
auth = () => this.authClient;
} }
export { InfisicalSDK, ApiClient }; // Export main SDK class
export { TDynamicSecretProvider, DynamicSecretProviders } from "./custom/schemas"; export { InfisicalSDK };
export type * from "./custom/secrets";
export type * from "./custom/dynamic-secrets"; export * from './api/types'
// Export types and enums from schemas
export {
TDynamicSecretProvider,
DynamicSecretProviders,
SqlProviders,
} from "./custom/schemas";