├── src ├── custom │ ├── schemas │ │ ├── index.ts │ │ └── dynamic-secrets.ts │ ├── constants.ts │ ├── environments.ts │ ├── projects.ts │ ├── folders.ts │ ├── errors.ts │ ├── dynamic-secrets.ts │ ├── util.ts │ ├── kms.ts │ ├── auth.ts │ └── secrets.ts ├── api │ ├── endpoints │ │ ├── environments.ts │ │ ├── folders.ts │ │ ├── projects.ts │ │ ├── auth.ts │ │ ├── secrets.ts │ │ ├── dynamic-secrets.ts │ │ └── kms.ts │ ├── types │ │ ├── index.ts │ │ ├── environments.ts │ │ ├── auth.ts │ │ ├── folders.ts │ │ ├── projects.ts │ │ ├── dynamic-secrets.ts │ │ ├── kms.ts │ │ └── secrets.ts │ └── base.ts └── index.ts ├── .gitattributes ├── tsconfig.json ├── test ├── aws_auth.ts ├── package.json ├── index.ts ├── package-lock.json ├── speccer.ts ├── test-kms.ts └── tsconfig.json ├── LICENSE ├── package.json ├── .github └── workflows │ └── release.yml ├── README.md ├── .gitignore └── img └── logoname-white.svg /src/custom/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./dynamic-secrets"; 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/custom/constants.ts: -------------------------------------------------------------------------------- 1 | export const MACHINE_IDENTITY_ID_ENV_NAME = "INFISICAL_MACHINE_IDENTITY_ID"; 2 | export const AWS_TOKEN_METADATA_URI = "http://169.254.169.254/latest/api/token"; 3 | export const AWS_IDENTITY_DOCUMENT_URI = "http://169.254.169.254/latest/dynamic/instance-identity/document"; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "rootDir": "./src", 7 | "outDir": "./lib", 8 | "strict": true, 9 | "resolveJsonModule": true, 10 | "removeComments": true, 11 | "esModuleInterop": true 12 | }, 13 | "include": [".", "src"], 14 | "exclude": ["examples", "node_modules", "lib", "test"] 15 | } 16 | -------------------------------------------------------------------------------- /test/aws_auth.ts: -------------------------------------------------------------------------------- 1 | import { InfisicalSDK } from "../src"; 2 | 3 | (async () => { 4 | const client = new InfisicalSDK({ 5 | siteUrl: "https://app.infisical.com" // Optional, defaults to https://app.infisical.com 6 | }); 7 | 8 | await client.auth().awsIamAuth.login({ 9 | identityId: "b1c540b8-4ca6-407e-8ce5-6696e8db50c4" 10 | }); 11 | 12 | console.log(client.auth().getAccessToken()); 13 | })(); 14 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "dependencies": { 13 | "fs": "^0.0.1-security", 14 | "https": "^1.0.0", 15 | "typescript": "^5.5.4" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^22.5.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/api/endpoints/environments.ts: -------------------------------------------------------------------------------- 1 | import { ApiClient } from "../base"; 2 | import { CreateEnvironmentRequest, CreateEnvironmentResponse } from "../types"; 3 | 4 | export class EnvironmentsApi { 5 | constructor(private apiClient: ApiClient) {} 6 | 7 | async create( 8 | data: CreateEnvironmentRequest 9 | ): Promise { 10 | return this.apiClient.post( 11 | `/api/v1/workspace/${data.projectId}/environments`, 12 | data 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/custom/environments.ts: -------------------------------------------------------------------------------- 1 | import { EnvironmentsApi } from "../api/endpoints/environments"; 2 | import { newInfisicalError } from "./errors"; 3 | import { CreateEnvironmentOptions } from "../api/types/environments"; 4 | 5 | export default class EnvironmentsClient { 6 | constructor(private apiClient: EnvironmentsApi) {} 7 | 8 | create = async (options: CreateEnvironmentOptions) => { 9 | try { 10 | const res = await this.apiClient.create(options); 11 | return res.environment; 12 | } catch (err) { 13 | throw newInfisicalError(err); 14 | } 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/api/types/index.ts: -------------------------------------------------------------------------------- 1 | import { Secret } from "./secrets"; 2 | 3 | export * from "./auth"; 4 | export * from "./secrets"; 5 | export * from "./dynamic-secrets"; 6 | export * from "./environments"; 7 | export * from "./projects"; 8 | export * from "./folders"; 9 | export * from "./kms"; 10 | 11 | export interface ApiResponse { 12 | statusCode: number; 13 | message: string; 14 | data: T; 15 | } 16 | 17 | export interface CreateSecretResponse { 18 | secret: Secret; 19 | } 20 | 21 | export interface UpdateSecretResponse { 22 | secret: Secret; 23 | } 24 | 25 | export interface DeleteSecretResponse { 26 | secret: Secret; 27 | } 28 | -------------------------------------------------------------------------------- /src/api/endpoints/folders.ts: -------------------------------------------------------------------------------- 1 | import { ApiClient } from "../base"; 2 | import { CreateFolderRequest, CreateFolderResponse, ListFoldersRequest, ListFoldersResponse } from "../types"; 3 | 4 | export class FoldersApi { 5 | constructor(private apiClient: ApiClient) {} 6 | 7 | async create(data: CreateFolderRequest): Promise { 8 | return this.apiClient.post("/api/v1/folders", data); 9 | } 10 | 11 | async listFolders(queryParams: ListFoldersRequest): Promise { 12 | return this.apiClient.get("/api/v1/folders", { 13 | params: queryParams 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/api/types/environments.ts: -------------------------------------------------------------------------------- 1 | export interface Environment { 2 | id: string; 3 | name: string; 4 | slug: string; 5 | position: number; 6 | projectId: string; 7 | createdAt: string; 8 | updatedAt: string; 9 | } 10 | 11 | export interface CreateEnvironmentRequest { 12 | name: string; 13 | projectId: string; 14 | slug: string; 15 | position?: number; 16 | } 17 | 18 | export type CreateEnvironmentResponse = { 19 | message: string; 20 | workspace: string; 21 | environment: Environment; 22 | }; 23 | 24 | export type CreateEnvironmentOptions = { 25 | name: string; 26 | projectId: string; 27 | slug: string; 28 | position?: number; 29 | }; 30 | -------------------------------------------------------------------------------- /src/api/types/auth.ts: -------------------------------------------------------------------------------- 1 | export interface UniversalAuthLoginRequest { 2 | clientId: string; 3 | clientSecret: string; 4 | } 5 | 6 | export interface UniversalAuthLoginResponse { 7 | accessToken: string; 8 | expiresIn: number; 9 | } 10 | 11 | export interface AwsIamAuthLoginRequest { 12 | identityId: string; 13 | iamHttpRequestMethod: string; 14 | iamRequestBody: string; 15 | iamRequestHeaders: string; 16 | } 17 | 18 | export interface AwsIamAuthLoginResponse { 19 | accessToken: string; 20 | expiresIn: number; 21 | } 22 | 23 | export interface TokenRenewRequest { 24 | accessToken: string; 25 | } 26 | 27 | export interface TokenRenewResponse { 28 | accessToken: string; 29 | expiresIn: number; 30 | } 31 | -------------------------------------------------------------------------------- /src/api/endpoints/projects.ts: -------------------------------------------------------------------------------- 1 | import { ApiClient } from "../base"; 2 | import { 3 | CreateProjectRequest, 4 | CreateProjectResponse, 5 | InviteMembersRequest, 6 | InviteMembersResponse, 7 | } from "../types"; 8 | 9 | export class ProjectsApi { 10 | constructor(private apiClient: ApiClient) {} 11 | 12 | async create(data: CreateProjectRequest): Promise { 13 | return this.apiClient.post( 14 | "/api/v2/workspace", 15 | data 16 | ); 17 | } 18 | 19 | async inviteMembers( 20 | data: InviteMembersRequest 21 | ): Promise { 22 | return this.apiClient.post( 23 | `/api/v2/workspace/${data.projectId}/memberships`, 24 | data 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/custom/projects.ts: -------------------------------------------------------------------------------- 1 | import { ProjectsApi } from "../api/endpoints/projects"; 2 | import { newInfisicalError } from "./errors"; 3 | import { CreateProjectOptions, InviteMemberToProjectOptions } from "../api/types/projects"; 4 | 5 | export default class ProjectsClient { 6 | constructor(private apiClient: ProjectsApi) {} 7 | 8 | create = async (options: CreateProjectOptions) => { 9 | try { 10 | const res = await this.apiClient.create(options); 11 | return res.project; 12 | } catch (err) { 13 | throw newInfisicalError(err); 14 | } 15 | }; 16 | 17 | inviteMembers = async (options: InviteMemberToProjectOptions) => { 18 | try { 19 | if (!options.usernames?.length && !options.emails?.length) { 20 | throw new Error("Either usernames or emails must be provided"); 21 | } 22 | 23 | const res = await this.apiClient.inviteMembers(options); 24 | return res.memberships; 25 | } catch (err) { 26 | throw newInfisicalError(err); 27 | } 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/api/endpoints/auth.ts: -------------------------------------------------------------------------------- 1 | import { ApiClient } from "../base"; 2 | import { 3 | UniversalAuthLoginRequest, 4 | UniversalAuthLoginResponse, 5 | AwsIamAuthLoginRequest, 6 | AwsIamAuthLoginResponse, 7 | TokenRenewRequest, 8 | TokenRenewResponse, 9 | } from "../types"; 10 | 11 | export class AuthApi { 12 | constructor(private apiClient: ApiClient) {} 13 | 14 | async universalAuthLogin( 15 | data: UniversalAuthLoginRequest 16 | ): Promise { 17 | return this.apiClient.post( 18 | "/api/v1/auth/universal-auth/login", 19 | data 20 | ); 21 | } 22 | 23 | async awsIamAuthLogin( 24 | data: AwsIamAuthLoginRequest 25 | ): Promise { 26 | return this.apiClient.post( 27 | "/api/v1/auth/aws-auth/login", 28 | data 29 | ); 30 | } 31 | 32 | async renewToken(data: TokenRenewRequest): Promise { 33 | return this.apiClient.post( 34 | "/api/v1/auth/token/renew", 35 | data 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/api/types/folders.ts: -------------------------------------------------------------------------------- 1 | export interface Folder { 2 | id: string; 3 | name: string; 4 | envId: string; 5 | description?: string; 6 | createdAt: string; 7 | updatedAt: string; 8 | parentId?: string; 9 | isReserved?: boolean; 10 | lastSecretModified?: string; 11 | version?: number; 12 | } 13 | 14 | export interface CreateFolderRequest { 15 | name: string; 16 | path: string; 17 | workspaceId: string; 18 | environment: string; 19 | description?: string; 20 | } 21 | 22 | export interface ListFoldersRequest { 23 | environment: string; 24 | workspaceId: string; 25 | path?: string; 26 | recursive?: boolean; 27 | lastSecretModified?: string; 28 | } 29 | 30 | export interface CreateFolderResponse { 31 | folder: Folder; 32 | } 33 | 34 | export interface ListFoldersResponse { 35 | folders: Folder[]; 36 | } 37 | 38 | export type CreateFolderOptions = { 39 | name: string; 40 | path: string; 41 | projectId: string; 42 | environment: string; 43 | description?: string; 44 | }; 45 | 46 | export type ListFoldersOptions = { 47 | environment: string; 48 | projectId: string; 49 | path?: string; 50 | recursive?: boolean; 51 | lastSecretModified?: string; 52 | }; 53 | -------------------------------------------------------------------------------- /src/custom/folders.ts: -------------------------------------------------------------------------------- 1 | import { FoldersApi } from "../api/endpoints/folders"; 2 | import { newInfisicalError } from "./errors"; 3 | import { CreateFolderOptions, ListFoldersOptions } from "../api/types/folders"; 4 | 5 | export default class FoldersClient { 6 | constructor(private apiClient: FoldersApi) {} 7 | 8 | create = async (options: CreateFolderOptions) => { 9 | try { 10 | const res = await this.apiClient.create({ 11 | name: options.name, 12 | path: options.path, 13 | workspaceId: options.projectId, 14 | environment: options.environment, 15 | description: options.description, 16 | }); 17 | return res.folder; 18 | } catch (err) { 19 | throw newInfisicalError(err); 20 | } 21 | }; 22 | 23 | listFolders = async (options: ListFoldersOptions) => { 24 | try { 25 | const res = await this.apiClient.listFolders({ 26 | environment: options.environment, 27 | workspaceId: options.projectId, 28 | path: options.path, 29 | recursive: options.recursive, 30 | lastSecretModified: options.lastSecretModified, 31 | }); 32 | return res.folders; 33 | } catch (err) { 34 | throw newInfisicalError(err); 35 | } 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/api/types/projects.ts: -------------------------------------------------------------------------------- 1 | export interface Project { 2 | id: string; 3 | name: string; 4 | slug: string; 5 | description?: string; 6 | type: string; 7 | createdAt: string; 8 | updatedAt: string; 9 | } 10 | 11 | export interface CreateProjectRequest { 12 | projectName: string; 13 | type: string; 14 | projectDescription?: string; 15 | slug?: string; 16 | template?: string; 17 | kmsKeyId?: string; 18 | } 19 | 20 | export interface CreateProjectResponse { 21 | project: Project; 22 | } 23 | 24 | export interface InviteMembersRequest { 25 | projectId: string; 26 | emails?: string[]; 27 | usernames?: string[]; 28 | roleSlugs?: string[]; 29 | } 30 | 31 | export interface Membership { 32 | id: string; 33 | userId: string; 34 | projectId: string; 35 | role: string; 36 | status: string; 37 | createdAt: string; 38 | updatedAt: string; 39 | } 40 | 41 | export interface InviteMembersResponse { 42 | memberships: Membership[]; 43 | } 44 | 45 | export type CreateProjectOptions = { 46 | projectName: string; 47 | type: string; 48 | projectDescription?: string; 49 | slug?: string; 50 | template?: string; 51 | kmsKeyId?: string; 52 | }; 53 | 54 | export type InviteMemberToProjectOptions = { 55 | projectId: string; 56 | emails?: string[]; 57 | usernames?: string[]; 58 | roleSlugs?: string[]; 59 | }; 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Infisical 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | NOTE: This license pertains specifically to components of the codebase that do not possess an explicit license. Each distinct SDK incorporated within this software is governed by its individual licensing terms and conditions. The provisions outlined in this MIT license are applicable to those segments of the codebase not explicitly covered by their respective licenses. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@infisical/sdk", 3 | "version": "0.0.0", 4 | "main": "./lib/index.js", 5 | "private": false, 6 | "files": [ 7 | "lib" 8 | ], 9 | "scripts": { 10 | "build": "tsup src/index.ts --out-dir lib --dts --format cjs,esm --tsconfig tsconfig.json --no-splitting" 11 | }, 12 | "keywords": [ 13 | "infisical", 14 | "open-source", 15 | "sdk", 16 | "typescript" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/infisical/infisical-node-sdk.git" 21 | }, 22 | "author": "Infisical Inc, ", 23 | "license": "ISC", 24 | "description": "The Infisical SDK provides a convenient way to programmatically interact with the Infisical API.", 25 | "devDependencies": { 26 | "@types/node": "^22.5.1", 27 | "tsc": "^2.0.4", 28 | "tsup": "^8.2.4" 29 | }, 30 | "dependencies": { 31 | "@aws-crypto/sha256-js": "^5.2.0", 32 | "@aws-sdk/credential-providers": "3.600.0", 33 | "@aws-sdk/protocol-http": "^3.370.0", 34 | "@aws-sdk/signature-v4": "^3.370.0", 35 | "axios": "^1.11.0", 36 | "typescript": "^5.5.4", 37 | "zod": "^3.23.8" 38 | }, 39 | "directories": { 40 | "lib": "lib", 41 | "test": "test" 42 | }, 43 | "types": "./lib/index.d.ts", 44 | "bugs": { 45 | "url": "https://github.com/infisical/infisical-node-sdk/issues" 46 | }, 47 | "homepage": "https://github.com/infisical/infisical-node-sdk#readme" 48 | } 49 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import { InfisicalSDK } from "../src"; 2 | 3 | (async () => { 4 | const client = new InfisicalSDK({ 5 | siteUrl: "http://localhost:8080" // Optional, defaults to https://app.infisical.com 6 | }); 7 | 8 | const EMAIL_TO_INVITE = ""; 9 | 10 | const universalAuthClientId = process.env.UNIVERSAL_AUTH_CLIENT_ID; 11 | const universalAuthClientSecret = process.env.UNIVERSAL_AUTH_CLIENT_SECRET; 12 | 13 | if (!universalAuthClientId || !universalAuthClientSecret) { 14 | throw new Error("UNIVERSAL_AUTH_CLIENT_ID and UNIVERSAL_AUTH_CLIENT_SECRET must be set"); 15 | } 16 | 17 | await client.auth().universalAuth.login({ 18 | clientId: universalAuthClientId, 19 | clientSecret: universalAuthClientSecret 20 | }); 21 | 22 | console.log("Creating project"); 23 | const project = await client.projects().create({ 24 | projectDescription: "test description", 25 | projectName: "test project1344assdfd", 26 | type: "secret-manager", 27 | slug: "test-project1assdfd43" 28 | }); 29 | 30 | const environment = await client.environments().create({ 31 | position: 100, 32 | slug: "test-environment-custom-slug", 33 | name: "test environment", 34 | projectId: project.id 35 | }); 36 | 37 | console.log("Creating folder"); 38 | const folder = await client.folders().create({ 39 | name: "test-folder", 40 | projectId: project.id, 41 | environment: environment.slug, 42 | path: "/" 43 | }); 44 | 45 | console.log("Inviting member to project"); 46 | const memberships = await client.projects().inviteMembers({ 47 | projectId: project.id, 48 | emails: [EMAIL_TO_INVITE], 49 | roleSlugs: ["admin"] 50 | }); 51 | 52 | console.log("Memberships", memberships); 53 | })(); 54 | -------------------------------------------------------------------------------- /src/custom/errors.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export class InfisicalSDKError extends Error { 4 | constructor(message: string) { 5 | super(message); 6 | this.message = message; 7 | this.name = "InfisicalSDKError"; 8 | } 9 | } 10 | 11 | export class InfisicalSDKRequestError extends Error { 12 | constructor( 13 | message: string, 14 | requestData: { 15 | url: string; 16 | method: string; 17 | statusCode: number; 18 | } 19 | ) { 20 | super(message); 21 | this.message = `[URL=${requestData.url}] [Method=${requestData.method}] [StatusCode=${requestData.statusCode}] ${message}`; 22 | this.name = "InfisicalSDKRequestError"; 23 | } 24 | } 25 | 26 | export const newInfisicalError = (error: any) => { 27 | if (axios.isAxiosError(error)) { 28 | const data = error?.response?.data; 29 | 30 | if (data?.message) { 31 | let message = data.message; 32 | if (error.response?.status === 422) { 33 | message = JSON.stringify(data); 34 | } 35 | 36 | return new InfisicalSDKRequestError(message, { 37 | url: error.response?.config.url || "", 38 | method: error.response?.config.method || "", 39 | statusCode: error.response?.status || 0, 40 | }); 41 | } else if (error.message) { 42 | return new InfisicalSDKError(error.message); 43 | } else if (error.code) { 44 | // 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 45 | return new InfisicalSDKError(error.code); 46 | } else { 47 | return new InfisicalSDKError("Request failed with unknown error"); 48 | } 49 | } 50 | 51 | return new InfisicalSDKError(error?.message || "An error occurred"); 52 | }; 53 | -------------------------------------------------------------------------------- /src/api/endpoints/secrets.ts: -------------------------------------------------------------------------------- 1 | import { ApiClient } from "../base"; 2 | import { 3 | ListSecretsRequest, 4 | ListSecretsResponse, 5 | GetSecretRequest, 6 | GetSecretResponse, 7 | CreateSecretRequest, 8 | CreateSecretResponse, 9 | UpdateSecretRequest, 10 | UpdateSecretResponse, 11 | DeleteSecretRequest, 12 | DeleteSecretResponse, 13 | } from "../types"; 14 | 15 | export class SecretsApi { 16 | constructor(private apiClient: ApiClient) {} 17 | 18 | async listSecrets(params: ListSecretsRequest): Promise { 19 | return this.apiClient.get("/api/v3/secrets/raw", { 20 | params, 21 | }); 22 | } 23 | 24 | async getSecret(params: GetSecretRequest): Promise { 25 | const { secretName, ...queryParams } = params; 26 | return this.apiClient.get( 27 | `/api/v3/secrets/raw/${encodeURIComponent(secretName)}`, 28 | { params } 29 | ); 30 | } 31 | 32 | async createSecret( 33 | secretName: string, 34 | data: CreateSecretRequest 35 | ): Promise { 36 | return this.apiClient.post( 37 | `/api/v3/secrets/raw/${encodeURIComponent(secretName)}`, 38 | data 39 | ); 40 | } 41 | 42 | async updateSecret( 43 | secretName: string, 44 | data: UpdateSecretRequest 45 | ): Promise { 46 | return this.apiClient.patch( 47 | `/api/v3/secrets/raw/${encodeURIComponent(secretName)}`, 48 | data 49 | ); 50 | } 51 | 52 | async deleteSecret( 53 | secretName: string, 54 | data: DeleteSecretRequest 55 | ): Promise { 56 | return this.apiClient.delete( 57 | `/api/v3/secrets/raw/${encodeURIComponent(secretName)}`, 58 | { data } 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release Node.js SDK 3 | run-name: Release Node.js SDK 4 | 5 | on: 6 | push: 7 | tags: 8 | - "*.*.*" # version, e.g. 1.0.0 9 | 10 | jobs: 11 | npm: 12 | name: Publish NPM 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 17 | 18 | - name: Setup Node 19 | uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 20 | with: 21 | node-version: 20 22 | cache: "npm" 23 | cache-dependency-path: package-lock.json 24 | 25 | - name: Install dependencies 26 | run: npm install 27 | 28 | - name: Set NPM version 29 | run: npm version ${{ github.ref_name }} --allow-same-version --no-git-tag-version 30 | 31 | - name: Build SDK 32 | run: npm run build 33 | 34 | - name: Setup NPM 35 | run: | 36 | echo 'registry="https://registry.npmjs.org/"' > ./.npmrc 37 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc 38 | 39 | echo 'registry="https://registry.npmjs.org/"' > ~/.npmrc 40 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 41 | env: 42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | 44 | - name: Pack NPM 45 | run: npm pack 46 | 47 | - name: Publish NPM 48 | run: npm publish --tarball=./infisical-sdk-${{github.ref_name}} --access public --registry=https://registry.npmjs.org/ 49 | env: 50 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 51 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 52 | -------------------------------------------------------------------------------- /src/api/endpoints/dynamic-secrets.ts: -------------------------------------------------------------------------------- 1 | import { ApiClient } from "../base"; 2 | import { 3 | CreateDynamicSecretRequest, 4 | CreateDynamicSecretResponse, 5 | DeleteDynamicSecretRequest, 6 | DeleteDynamicSecretResponse, 7 | CreateLeaseRequest, 8 | CreateLeaseResponse, 9 | DeleteLeaseRequest, 10 | DeleteLeaseResponse, 11 | RenewLeaseRequest, 12 | RenewLeaseResponse, 13 | } from "../types"; 14 | 15 | export class DynamicSecretsApi { 16 | constructor(private apiClient: ApiClient) {} 17 | 18 | async create( 19 | data: CreateDynamicSecretRequest 20 | ): Promise { 21 | return this.apiClient.post( 22 | "/api/v1/dynamic-secrets", 23 | data 24 | ); 25 | } 26 | 27 | async delete( 28 | secretName: string, 29 | data: DeleteDynamicSecretRequest 30 | ): Promise { 31 | return this.apiClient.delete( 32 | `/api/v1/dynamic-secrets/${encodeURIComponent(secretName)}`, 33 | { data } 34 | ); 35 | } 36 | 37 | leases = { 38 | create: async (data: CreateLeaseRequest): Promise => { 39 | return this.apiClient.post( 40 | "/api/v1/dynamic-secrets/leases", 41 | data 42 | ); 43 | }, 44 | 45 | delete: async ( 46 | leaseId: string, 47 | data: DeleteLeaseRequest 48 | ): Promise => { 49 | return this.apiClient.delete( 50 | `/api/v1/dynamic-secrets/leases/${leaseId}`, 51 | { data } 52 | ); 53 | }, 54 | 55 | renew: async ( 56 | leaseId: string, 57 | data: RenewLeaseRequest 58 | ): Promise => { 59 | return this.apiClient.post( 60 | `/api/v1/dynamic-secrets/leases/${leaseId}/renew`, 61 | data 62 | ); 63 | }, 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/custom/dynamic-secrets.ts: -------------------------------------------------------------------------------- 1 | import { DynamicSecretsApi } from "../api/endpoints/dynamic-secrets"; 2 | import { TDynamicSecretProvider } from "./schemas/dynamic-secrets"; 3 | import { newInfisicalError } from "./errors"; 4 | import { 5 | CreateDynamicSecretOptions, 6 | DeleteDynamicSecretOptions, 7 | CreateDynamicSecretLeaseOptions, 8 | DeleteDynamicSecretLeaseOptions, 9 | RenewDynamicSecretLeaseOptions, 10 | } from "../api/types/dynamic-secrets"; 11 | 12 | export default class DynamicSecretsClient { 13 | constructor(private apiClient: DynamicSecretsApi) {} 14 | 15 | async create(options: CreateDynamicSecretOptions) { 16 | try { 17 | const res = await this.apiClient.create(options); 18 | return res.dynamicSecret; 19 | } catch (err) { 20 | throw newInfisicalError(err); 21 | } 22 | } 23 | 24 | async delete(dynamicSecretName: string, options: DeleteDynamicSecretOptions) { 25 | try { 26 | const res = await this.apiClient.delete(dynamicSecretName, options); 27 | return res.dynamicSecret; 28 | } catch (err) { 29 | throw newInfisicalError(err); 30 | } 31 | } 32 | 33 | leases = { 34 | create: async (options: CreateDynamicSecretLeaseOptions) => { 35 | try { 36 | const res = await this.apiClient.leases.create(options); 37 | return res; 38 | } catch (err) { 39 | throw newInfisicalError(err); 40 | } 41 | }, 42 | 43 | delete: async ( 44 | leaseId: string, 45 | options: DeleteDynamicSecretLeaseOptions 46 | ) => { 47 | try { 48 | const res = await this.apiClient.leases.delete(leaseId, options); 49 | return res; 50 | } catch (err) { 51 | throw newInfisicalError(err); 52 | } 53 | }, 54 | 55 | renew: async (leaseId: string, options: RenewDynamicSecretLeaseOptions) => { 56 | try { 57 | const res = await this.apiClient.leases.renew(leaseId, options); 58 | return res; 59 | } catch (err) { 60 | throw newInfisicalError(err); 61 | } 62 | }, 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | infisical 3 |

4 |

5 |

Infisical Node.js SDK

6 |

7 | | 8 | Documentation | 9 | Website | 10 | Slack | 11 |

12 | 13 |

14 | 15 | Infisical SDK's are released under the MIT license. 16 | 17 | 18 | Slack community channel 19 | 20 | 21 | Infisical Twitter 22 | 23 |

24 | 25 | ## Introduction 26 | 27 | **[Infisical](https://infisical.com)** is the open source secret management platform that teams use to centralize their secrets like API keys, database credentials, and configurations. 28 | 29 | If you’re working with Node.js, the official Infisical Node.js SDK package is the easiest way to fetch and work with secrets for your application. You can read the documentation [here](https://infisical.com/docs/sdks/languages/node). 30 | 31 | ## Documentation 32 | You can find the documentation for the Node.js SDK on our [SDK documentation page](https://infisical.com/docs/sdks/languages/node). 33 | 34 | ## Security 35 | 36 | Please do not file GitHub issues or post on our public forum for security vulnerabilities, as they are public! 37 | 38 | Infisical takes security issues very seriously. If you have any concerns about Infisical or believe you have uncovered a vulnerability, please get in touch via the e-mail address security@infisical.com. In the message, try to provide a description of the issue and ideally a way of reproducing it. The security team will get back to you as soon as possible. 39 | 40 | Note that this security address should be used only for undisclosed vulnerabilities. Please report any security problems to us before disclosing it publicly. -------------------------------------------------------------------------------- /test/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "test", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "fs": "^0.0.1-security", 13 | "https": "^1.0.0", 14 | "typescript": "^5.5.4" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^22.5.1" 18 | } 19 | }, 20 | "node_modules/@types/node": { 21 | "version": "22.5.1", 22 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", 23 | "integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==", 24 | "dev": true, 25 | "license": "MIT", 26 | "dependencies": { 27 | "undici-types": "~6.19.2" 28 | } 29 | }, 30 | "node_modules/fs": { 31 | "version": "0.0.1-security", 32 | "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", 33 | "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", 34 | "license": "ISC" 35 | }, 36 | "node_modules/https": { 37 | "version": "1.0.0", 38 | "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", 39 | "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==", 40 | "license": "ISC" 41 | }, 42 | "node_modules/typescript": { 43 | "version": "5.5.4", 44 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", 45 | "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", 46 | "license": "Apache-2.0", 47 | "bin": { 48 | "tsc": "bin/tsc", 49 | "tsserver": "bin/tsserver" 50 | }, 51 | "engines": { 52 | "node": ">=14.17" 53 | } 54 | }, 55 | "node_modules/undici-types": { 56 | "version": "6.19.8", 57 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 58 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 59 | "dev": true, 60 | "license": "MIT" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/api/endpoints/kms.ts: -------------------------------------------------------------------------------- 1 | import { ApiClient } from "../base"; 2 | import { 3 | CreateKmsKeyOptions, 4 | CreateKmsKeyResponse, 5 | DeleteKmsKeyOptions, 6 | DeleteKmsKeyResponse, 7 | GetKmsKeyByNameOptions, 8 | GetKmsKeyByNameResponse, 9 | KmsDecryptDataOptions, 10 | KmsDecryptDataResponse, 11 | KmsEncryptDataOptions, 12 | KmsEncryptDataResponse, 13 | KmsGetPublicKeyOptions, 14 | KmsGetPublicKeyResponse, 15 | KmsListSigningAlgorithmsOptions, 16 | KmsListSigningAlgorithmsResponse, 17 | KmsSignDataOptions, 18 | KmsSignDataResponse, 19 | KmsVerifyDataOptions, 20 | KmsVerifyDataResponse 21 | } from "../types/kms"; 22 | 23 | export class KmsApi { 24 | constructor(private apiClient: ApiClient) {} 25 | 26 | async createKmsKey(data: CreateKmsKeyOptions): Promise { 27 | return this.apiClient.post("/api/v1/kms/keys", data); 28 | } 29 | 30 | async deleteKmsKey(data: DeleteKmsKeyOptions): Promise { 31 | return this.apiClient.delete(`/api/v1/kms/keys/${data.keyId}`); 32 | } 33 | 34 | async getKmsKeyByName(data: GetKmsKeyByNameOptions): Promise { 35 | return this.apiClient.get(`/api/v1/kms/keys/key-name/${encodeURIComponent(data.name)}?projectId=${data.projectId}`); 36 | } 37 | 38 | async encryptData(data: KmsEncryptDataOptions): Promise { 39 | return this.apiClient.post(`/api/v1/kms/keys/${data.keyId}/encrypt`, data); 40 | } 41 | 42 | async decryptData(data: KmsDecryptDataOptions): Promise { 43 | return this.apiClient.post(`/api/v1/kms/keys/${data.keyId}/decrypt`, data); 44 | } 45 | 46 | async signData(data: KmsSignDataOptions): Promise { 47 | return this.apiClient.post(`/api/v1/kms/keys/${data.keyId}/sign`, data); 48 | } 49 | 50 | async verifyData(data: KmsVerifyDataOptions): Promise { 51 | return this.apiClient.post(`/api/v1/kms/keys/${data.keyId}/verify`, data); 52 | } 53 | 54 | async listSigningAlgorithms(data: KmsListSigningAlgorithmsOptions): Promise { 55 | return this.apiClient.get(`/api/v1/kms/keys/${data.keyId}/signing-algorithms`); 56 | } 57 | 58 | async getSigningPublicKey(data: KmsGetPublicKeyOptions): Promise { 59 | return this.apiClient.get(`/api/v1/kms/keys/${data.keyId}/public-key`); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/speccer.ts: -------------------------------------------------------------------------------- 1 | import https from "https"; 2 | import fs from "fs"; 3 | 4 | const specUrl = "https://app.infisical.com/api/docs/json"; 5 | const outputFile = "filtered-spec.json"; 6 | 7 | // List of endpoints you want to keep, with their HTTP methods 8 | 9 | interface Endpoint { 10 | path: string; 11 | method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; 12 | } 13 | 14 | const desiredEndpoints: Endpoint[] = [ 15 | // Identities 16 | { path: "/api/v1/identities", method: "POST" }, 17 | { path: "/api/v1/identities/{identityId}", method: "PATCH" }, 18 | { path: "/api/v1/identities/{identityId}", method: "DELETE" }, 19 | { path: "/api/v1/identities/{identityId}", method: "GET" }, 20 | { path: "/api/v1/identities", method: "GET" }, 21 | 22 | // Token Auth 23 | { path: "/api/v1/auth/token-auth/identities/{identityId}", method: "POST" }, 24 | { path: "/api/v1/auth/token-auth/identities/{identityId}", method: "GET" }, 25 | { path: "/api/v1/auth/token-auth/identities/{identityId}", method: "PATCH" }, 26 | { path: "/api/v1/auth/token-auth/identities/{identityId}", method: "DELETE" }, 27 | { path: "/api/v1/auth/token-auth/identities/{identityId}/tokens", method: "GET" }, 28 | { path: "/api/v1/auth/token-auth/identities/{identityId}/tokens", method: "POST" }, 29 | { path: "/api/v1/auth/token-auth/tokens/{tokenId}", method: "PATCH" }, 30 | { path: "/v1/auth/token-auth/tokens/{tokenId}/revoke", method: "POST" }, 31 | 32 | ]; 33 | 34 | https 35 | .get(specUrl, res => { 36 | let data = ""; 37 | 38 | res.on("data", chunk => { 39 | data += chunk; 40 | }); 41 | 42 | res.on("end", () => { 43 | const spec = JSON.parse(data); 44 | 45 | // Filter the paths object 46 | spec.paths = Object.keys(spec.paths).reduce((filteredPaths, path) => { 47 | const matchingEndpoints = desiredEndpoints.filter( 48 | endpoint => endpoint.path === path && spec.paths[path][endpoint.method.toLowerCase()] 49 | ); 50 | 51 | if (matchingEndpoints.length > 0) { 52 | // @ts-expect-error 53 | filteredPaths[path] = {}; 54 | matchingEndpoints.forEach(endpoint => { 55 | // @ts-expect-error 56 | filteredPaths[path][endpoint.method.toLowerCase()] = spec.paths[path][endpoint.method.toLowerCase()]; 57 | }); 58 | } 59 | 60 | return filteredPaths; 61 | }, {}); 62 | 63 | // Write the filtered spec to a file 64 | fs.writeFileSync(outputFile, JSON.stringify(spec, null, 2)); 65 | console.log(`Filtered spec written to ${outputFile}`); 66 | }); 67 | }) 68 | .on("error", err => { 69 | console.error("Error fetching spec:", err.message); 70 | }); 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | /src/infisicalapi_client 133 | /lib 134 | 135 | .DS_Store 136 | /test/pytest -------------------------------------------------------------------------------- /src/api/base.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 2 | 3 | export interface ApiClientConfig { 4 | baseURL: string; 5 | headers?: Record; 6 | timeout?: number; 7 | } 8 | 9 | export class ApiClient { 10 | private client: AxiosInstance; 11 | 12 | constructor(config: ApiClientConfig) { 13 | this.client = axios.create({ 14 | baseURL: config.baseURL, 15 | headers: config.headers || {}, 16 | timeout: config.timeout || 10000, 17 | }); 18 | 19 | this.setupRetryInterceptor(); 20 | } 21 | 22 | private setupRetryInterceptor() { 23 | const maxRetries = 4; 24 | const initialRetryDelay = 1000; 25 | const backoffFactor = 2; 26 | 27 | this.client.interceptors.response.use(null, (error) => { 28 | const config = error?.config; 29 | if (!config) return Promise.reject(error); 30 | 31 | if (!config._retryCount) config._retryCount = 0; 32 | 33 | // handle rate limits and network errors 34 | if ( 35 | (error.response?.status === 429 || 36 | error.response?.status === undefined) && 37 | config._retryCount < maxRetries 38 | ) { 39 | config._retryCount++; 40 | const baseDelay = 41 | initialRetryDelay * Math.pow(backoffFactor, config._retryCount - 1); 42 | const jitter = baseDelay * 0.2; 43 | const exponentialDelay = baseDelay + (Math.random() * 2 - 1) * jitter; 44 | 45 | return new Promise((resolve) => { 46 | setTimeout(() => resolve(this.client(config)), exponentialDelay); 47 | }); 48 | } 49 | 50 | return Promise.reject(error); 51 | }); 52 | } 53 | 54 | public setAccessToken(token: string) { 55 | this.client.defaults.headers.common["Authorization"] = `Bearer ${token}`; 56 | } 57 | 58 | public async get(url: string, config?: AxiosRequestConfig): Promise { 59 | const response: AxiosResponse = await this.client.get(url, config); 60 | return response.data; 61 | } 62 | 63 | public async post( 64 | url: string, 65 | data?: any, 66 | config?: AxiosRequestConfig 67 | ): Promise { 68 | const response: AxiosResponse = await this.client.post( 69 | url, 70 | data, 71 | config 72 | ); 73 | return response.data; 74 | } 75 | 76 | public async patch( 77 | url: string, 78 | data?: any, 79 | config?: AxiosRequestConfig 80 | ): Promise { 81 | const response: AxiosResponse = await this.client.patch( 82 | url, 83 | data, 84 | config 85 | ); 86 | return response.data; 87 | } 88 | 89 | public async delete(url: string, config?: AxiosRequestConfig): Promise { 90 | const response: AxiosResponse = await this.client.delete(url, config); 91 | return response.data; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/custom/util.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { AWS_IDENTITY_DOCUMENT_URI, AWS_TOKEN_METADATA_URI } from "./constants"; 3 | 4 | import { Sha256 } from "@aws-crypto/sha256-js"; 5 | import { fromNodeProviderChain } from "@aws-sdk/credential-providers"; 6 | import { HttpRequest } from "@aws-sdk/protocol-http"; 7 | import { SignatureV4 } from "@aws-sdk/signature-v4"; 8 | 9 | import { InfisicalSDKError } from "./errors"; 10 | import { Secret } from "../api/types"; 11 | 12 | export const getUniqueSecretsByKey = (secrets: Secret[]) => { 13 | const secretMap = new Map(); 14 | 15 | for (const secret of secrets) { 16 | secretMap.set(secret.secretKey, secret); 17 | } 18 | 19 | return Array.from(secretMap.values()); 20 | }; 21 | 22 | export const getAwsRegion = async () => { 23 | const region = process.env.AWS_REGION; // Typically found in lambda runtime environment 24 | if (region) { 25 | return region; 26 | } 27 | 28 | try { 29 | const tokenRes = await axios.put(AWS_TOKEN_METADATA_URI, undefined, { 30 | headers: { 31 | "X-aws-ec2-metadata-token-ttl-seconds": "21600" 32 | }, 33 | timeout: 5_000 // 5 seconds 34 | }); 35 | 36 | const identityResponse = await axios.get<{ region: string }>(AWS_IDENTITY_DOCUMENT_URI, { 37 | headers: { 38 | "X-aws-ec2-metadata-token": tokenRes.data, 39 | Accept: "application/json" 40 | }, 41 | timeout: 5_000 42 | }); 43 | 44 | return identityResponse.data.region; 45 | } catch (e) { 46 | throw e; 47 | } 48 | }; 49 | 50 | export const performAwsIamLogin = async (region: string) => { 51 | const credentials = await fromNodeProviderChain()(); 52 | 53 | if (!credentials.accessKeyId || !credentials.secretAccessKey) { 54 | throw new InfisicalSDKError("Credentials not found"); 55 | } 56 | 57 | const iamRequestURL = `https://sts.${region}.amazonaws.com/`; 58 | const iamRequestBody = "Action=GetCallerIdentity&Version=2011-06-15"; 59 | const iamRequestHeaders = { 60 | "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", 61 | Host: `sts.${region}.amazonaws.com` 62 | }; 63 | 64 | const request = new HttpRequest({ 65 | protocol: "https:", 66 | hostname: `sts.${region}.amazonaws.com`, 67 | path: "/", 68 | method: "POST", 69 | headers: { 70 | ...iamRequestHeaders, 71 | "Content-Length": String(Buffer.byteLength(iamRequestBody)) 72 | }, 73 | body: iamRequestBody 74 | }); 75 | 76 | const signer = new SignatureV4({ 77 | credentials, 78 | region, 79 | service: "sts", 80 | sha256: Sha256 81 | }); 82 | 83 | const signedRequest = await signer.sign(request); 84 | 85 | const headers: Record = {}; 86 | Object.entries(signedRequest.headers).forEach(([key, value]) => { 87 | if (typeof value === "string") { 88 | // Normalize Authorization header to proper case 89 | const normalizedKey = key.toLowerCase() === "authorization" ? "Authorization" : key; 90 | headers[normalizedKey] = value; 91 | } 92 | }); 93 | 94 | return { 95 | iamHttpRequestMethod: "POST", 96 | iamRequestUrl: iamRequestURL, 97 | iamRequestBody: iamRequestBody, 98 | iamRequestHeaders: headers 99 | }; 100 | }; 101 | -------------------------------------------------------------------------------- /src/api/types/dynamic-secrets.ts: -------------------------------------------------------------------------------- 1 | import { DynamicSecretProviders } from "../../custom/schemas"; 2 | import { TDynamicSecretProvider } from "../../custom/schemas"; 3 | 4 | export interface CreateDynamicSecretRequest { 5 | provider: TDynamicSecretProvider; 6 | defaultTTL: string; 7 | maxTTL: string; 8 | name: string; 9 | projectSlug: string; 10 | environmentSlug: string; 11 | } 12 | 13 | export interface DynamicSecret { 14 | id: string; 15 | name: string; 16 | defaultTTL: string; 17 | maxTTL: string; 18 | provider: { 19 | type: DynamicSecretProviders; 20 | inputs: Record; 21 | }; 22 | createdAt: string; 23 | updatedAt: string; 24 | version: number; 25 | type: string; 26 | folderId: string; 27 | status: string; 28 | statusDetails: string; 29 | projectGatewayId: string; 30 | metadata: Record; 31 | } 32 | 33 | export interface CreateDynamicSecretResponse { 34 | dynamicSecret: DynamicSecret; 35 | } 36 | 37 | export interface DeleteDynamicSecretRequest { 38 | environmentSlug: string; 39 | projectSlug: string; 40 | path?: string; 41 | isForced?: boolean; 42 | } 43 | 44 | export interface DeleteDynamicSecretResponse { 45 | dynamicSecret: DynamicSecret; 46 | } 47 | 48 | export interface CreateLeaseRequest { 49 | dynamicSecretName: string; 50 | environmentSlug: string; 51 | projectSlug: string; 52 | path?: string; 53 | ttl?: string; 54 | } 55 | 56 | export interface Lease { 57 | id: string; 58 | dynamicSecretId: string; 59 | data: Record; 60 | expiresAt: string; 61 | createdAt: string; 62 | updatedAt: string; 63 | } 64 | 65 | export interface CreateLeaseResponse { 66 | lease: Lease; 67 | } 68 | 69 | export interface DeleteLeaseRequest { 70 | environmentSlug: string; 71 | projectSlug: string; 72 | path?: string; 73 | isForced?: boolean; 74 | } 75 | 76 | export interface DeleteLeaseResponse { 77 | lease: Lease; 78 | } 79 | 80 | export interface RenewLeaseRequest { 81 | environmentSlug: string; 82 | projectSlug: string; 83 | path?: string; 84 | ttl?: string; 85 | } 86 | 87 | export interface RenewLeaseResponse { 88 | lease: Lease; 89 | } 90 | 91 | export type CreateDynamicSecretOptions = { 92 | provider: TDynamicSecretProvider; 93 | defaultTTL: string; 94 | maxTTL: string; 95 | name: string; 96 | projectSlug: string; 97 | environmentSlug: string; 98 | path?: string; 99 | metadata?: Record; 100 | }; 101 | 102 | export type DeleteDynamicSecretOptions = { 103 | environmentSlug: string; 104 | projectSlug: string; 105 | path?: string; 106 | isForced?: boolean; 107 | }; 108 | 109 | export type CreateDynamicSecretLeaseOptions = { 110 | dynamicSecretName: string; 111 | environmentSlug: string; 112 | projectSlug: string; 113 | path?: string; 114 | ttl?: string; 115 | }; 116 | 117 | export type DeleteDynamicSecretLeaseOptions = { 118 | environmentSlug: string; 119 | projectSlug: string; 120 | path?: string; 121 | isForced?: boolean; 122 | }; 123 | 124 | export type RenewDynamicSecretLeaseOptions = { 125 | environmentSlug: string; 126 | projectSlug: string; 127 | path?: string; 128 | ttl?: string; 129 | }; 130 | -------------------------------------------------------------------------------- /src/custom/kms.ts: -------------------------------------------------------------------------------- 1 | import { newInfisicalError } from "./errors"; 2 | import { 3 | CreateKmsKeyOptions, 4 | DeleteKmsKeyOptions, 5 | GetKmsKeyByNameOptions, 6 | KmsDecryptDataOptions, 7 | KmsEncryptDataOptions, 8 | KmsGetPublicKeyOptions, 9 | KmsListSigningAlgorithmsOptions, 10 | KmsSignDataOptions, 11 | KmsVerifyDataOptions 12 | } from "../api/types/kms"; 13 | import { KmsApi } from "../api/endpoints/kms"; 14 | 15 | export default class KmsClient { 16 | constructor(private apiClient: KmsApi) {} 17 | 18 | keys = () => { 19 | const create = async (options: CreateKmsKeyOptions) => { 20 | try { 21 | const res = await this.apiClient.createKmsKey(options); 22 | return res.key; 23 | } catch (err) { 24 | throw newInfisicalError(err); 25 | } 26 | }; 27 | 28 | const deleteKmsKey = async (options: DeleteKmsKeyOptions) => { 29 | try { 30 | const res = await this.apiClient.deleteKmsKey(options); 31 | return res.key; 32 | } catch (err) { 33 | throw newInfisicalError(err); 34 | } 35 | }; 36 | 37 | const getByName = async (options: GetKmsKeyByNameOptions) => { 38 | try { 39 | const res = await this.apiClient.getKmsKeyByName(options); 40 | 41 | return res.key; 42 | } catch (err) { 43 | throw newInfisicalError(err); 44 | } 45 | }; 46 | 47 | return { 48 | create, 49 | delete: deleteKmsKey, 50 | getByName 51 | }; 52 | }; 53 | 54 | encryption = () => { 55 | const encrypt = async (options: KmsEncryptDataOptions) => { 56 | try { 57 | const res = await this.apiClient.encryptData(options); 58 | return res.ciphertext; 59 | } catch (err) { 60 | throw newInfisicalError(err); 61 | } 62 | }; 63 | 64 | const decrypt = async (options: KmsDecryptDataOptions) => { 65 | try { 66 | const res = await this.apiClient.decryptData(options); 67 | return res.plaintext; 68 | } catch (err) { 69 | throw newInfisicalError(err); 70 | } 71 | }; 72 | 73 | return { 74 | encrypt, 75 | decrypt 76 | }; 77 | }; 78 | 79 | signing = () => { 80 | const sign = async (options: KmsSignDataOptions) => { 81 | try { 82 | const res = await this.apiClient.signData(options); 83 | return res; 84 | } catch (err) { 85 | throw newInfisicalError(err); 86 | } 87 | }; 88 | 89 | const verify = async (options: KmsVerifyDataOptions) => { 90 | try { 91 | const res = await this.apiClient.verifyData(options); 92 | return res; 93 | } catch (err) { 94 | throw newInfisicalError(err); 95 | } 96 | }; 97 | 98 | const listSigningAlgorithms = async (options: KmsListSigningAlgorithmsOptions) => { 99 | try { 100 | const res = await this.apiClient.listSigningAlgorithms(options); 101 | return res.signingAlgorithms; 102 | } catch (err) { 103 | throw newInfisicalError(err); 104 | } 105 | }; 106 | 107 | const getPublicKey = async (options: KmsGetPublicKeyOptions) => { 108 | try { 109 | const res = await this.apiClient.getSigningPublicKey(options); 110 | return res.publicKey; 111 | } catch (err) { 112 | throw newInfisicalError(err); 113 | } 114 | }; 115 | 116 | return { 117 | sign, 118 | verify, 119 | listSigningAlgorithms, 120 | getPublicKey 121 | }; 122 | }; 123 | } 124 | -------------------------------------------------------------------------------- /src/api/types/kms.ts: -------------------------------------------------------------------------------- 1 | export enum EncryptionAlgorithm { 2 | RSA_4096 = "RSA_4096", 3 | ECC_NIST_P256 = "ECC_NIST_P256", 4 | AES_256_GCM = "aes-256-gcm", 5 | AES_128_GCM = "aes-128-gcm" 6 | } 7 | 8 | export enum SigningAlgorithm { 9 | // RSA PSS algorithms 10 | // These are NOT deterministic and include randomness. 11 | // This means that the output signature is different each time for the same input. 12 | RSASSA_PSS_SHA_512 = "RSASSA_PSS_SHA_512", 13 | RSASSA_PSS_SHA_384 = "RSASSA_PSS_SHA_384", 14 | RSASSA_PSS_SHA_256 = "RSASSA_PSS_SHA_256", 15 | 16 | // RSA PKCS#1 v1.5 algorithms 17 | // These are deterministic and the output is the same each time for the same input. 18 | RSASSA_PKCS1_V1_5_SHA_512 = "RSASSA_PKCS1_V1_5_SHA_512", 19 | RSASSA_PKCS1_V1_5_SHA_384 = "RSASSA_PKCS1_V1_5_SHA_384", 20 | RSASSA_PKCS1_V1_5_SHA_256 = "RSASSA_PKCS1_V1_5_SHA_256", 21 | 22 | // ECDSA algorithms 23 | // None of these are deterministic and include randomness like RSA PSS. 24 | ECDSA_SHA_512 = "ECDSA_SHA_512", 25 | ECDSA_SHA_384 = "ECDSA_SHA_384", 26 | ECDSA_SHA_256 = "ECDSA_SHA_256" 27 | } 28 | 29 | export enum KeyUsage { 30 | ENCRYPTION = "encrypt-decrypt", 31 | SIGNING = "sign-verify" 32 | } 33 | 34 | export interface KmsKey { 35 | id: string; 36 | description: string; 37 | isDisabled: boolean; 38 | orgId: string; 39 | name: string; 40 | projectId: string; 41 | keyUsage: KeyUsage; 42 | version: number; 43 | encryptionAlgorithm: EncryptionAlgorithm; 44 | } 45 | 46 | export interface CreateKmsKeyOptions { 47 | projectId: string; 48 | name: string; 49 | description?: string; 50 | 51 | keyUsage: KeyUsage; 52 | encryptionAlgorithm: EncryptionAlgorithm; 53 | } 54 | 55 | export interface CreateKmsKeyResponse { 56 | key: KmsKey; 57 | } 58 | 59 | export interface DeleteKmsKeyOptions { 60 | keyId: string; 61 | } 62 | 63 | export interface DeleteKmsKeyResponse { 64 | key: KmsKey; 65 | } 66 | 67 | export interface GetKmsKeyByNameOptions { 68 | projectId: string; 69 | name: string; 70 | } 71 | 72 | export interface GetKmsKeyByNameResponse { 73 | key: KmsKey; 74 | } 75 | 76 | export interface KmsEncryptDataOptions { 77 | keyId: string; 78 | plaintext: string; 79 | } 80 | 81 | export interface KmsEncryptDataResponse { 82 | ciphertext: string; 83 | } 84 | 85 | export interface KmsDecryptDataOptions { 86 | keyId: string; 87 | ciphertext: string; 88 | } 89 | 90 | export interface KmsDecryptDataResponse { 91 | plaintext: string; 92 | } 93 | 94 | export interface KmsSignDataOptions { 95 | keyId: string; 96 | data: string; 97 | signingAlgorithm: SigningAlgorithm; 98 | isDigest?: boolean; 99 | } 100 | 101 | export interface KmsSignDataResponse { 102 | signature: string; 103 | keyId: string; 104 | signingAlgorithm: SigningAlgorithm; 105 | } 106 | 107 | export interface KmsVerifyDataOptions { 108 | keyId: string; 109 | data: string; // must be base64 encoded 110 | signature: string; 111 | signingAlgorithm: SigningAlgorithm; 112 | isDigest?: boolean; 113 | } 114 | 115 | export interface KmsVerifyDataResponse { 116 | signatureValid: boolean; 117 | keyId: string; 118 | signingAlgorithm: SigningAlgorithm; 119 | } 120 | 121 | export interface KmsListSigningAlgorithmsOptions { 122 | keyId: string; 123 | } 124 | 125 | export interface KmsListSigningAlgorithmsResponse { 126 | signingAlgorithms: SigningAlgorithm[]; 127 | } 128 | 129 | export interface KmsGetPublicKeyOptions { 130 | keyId: string; 131 | } 132 | 133 | export interface KmsGetPublicKeyResponse { 134 | publicKey: string; 135 | } 136 | -------------------------------------------------------------------------------- /src/custom/auth.ts: -------------------------------------------------------------------------------- 1 | import { InfisicalSDK } from ".."; 2 | import { AuthApi } from "../api/endpoints/auth"; 3 | import { UniversalAuthLoginRequest } from "../api/types"; 4 | import { MACHINE_IDENTITY_ID_ENV_NAME } from "./constants"; 5 | import { InfisicalSDKError, newInfisicalError } from "./errors"; 6 | import { getAwsRegion, performAwsIamLogin } from "./util"; 7 | 8 | type AuthenticatorFunction = (accessToken: string) => InfisicalSDK; 9 | 10 | type AwsAuthLoginOptions = { 11 | identityId?: string; 12 | }; 13 | 14 | export const renewToken = async (apiClient: AuthApi, token?: string) => { 15 | try { 16 | if (!token) { 17 | throw new InfisicalSDKError( 18 | "Unable to renew access token, no access token set." 19 | ); 20 | } 21 | 22 | const res = await apiClient.renewToken({ accessToken: token }); 23 | return res; 24 | } catch (err) { 25 | throw newInfisicalError(err); 26 | } 27 | }; 28 | 29 | export default class AuthClient { 30 | constructor( 31 | private sdkAuthenticator: AuthenticatorFunction, 32 | private apiClient: AuthApi, 33 | private _accessToken?: string 34 | ) {} 35 | 36 | awsIamAuth = { 37 | login: async (options?: AwsAuthLoginOptions) => { 38 | try { 39 | const identityId = 40 | options?.identityId || process.env[MACHINE_IDENTITY_ID_ENV_NAME]; 41 | if (!identityId) { 42 | throw new InfisicalSDKError( 43 | "Identity ID is required for AWS IAM authentication" 44 | ); 45 | } 46 | 47 | const iamRequest = await performAwsIamLogin(await getAwsRegion()); 48 | const res = await this.apiClient.awsIamAuthLogin({ 49 | iamHttpRequestMethod: iamRequest.iamHttpRequestMethod, 50 | iamRequestBody: Buffer.from(iamRequest.iamRequestBody).toString( 51 | "base64" 52 | ), 53 | iamRequestHeaders: Buffer.from( 54 | JSON.stringify(iamRequest.iamRequestHeaders) 55 | ).toString("base64"), 56 | identityId, 57 | }); 58 | 59 | return this.sdkAuthenticator(res.accessToken); 60 | } catch (err) { 61 | throw newInfisicalError(err); 62 | } 63 | }, 64 | renew: async () => { 65 | try { 66 | const refreshedToken = await renewToken( 67 | this.apiClient, 68 | this._accessToken 69 | ); 70 | return this.sdkAuthenticator(refreshedToken.accessToken); 71 | } catch (err) { 72 | throw newInfisicalError(err); 73 | } 74 | }, 75 | }; 76 | 77 | universalAuth = { 78 | login: async (options: UniversalAuthLoginRequest) => { 79 | try { 80 | const res = await this.apiClient.universalAuthLogin(options); 81 | return this.sdkAuthenticator(res.accessToken); 82 | } catch (err) { 83 | throw newInfisicalError(err); 84 | } 85 | }, 86 | renew: async () => { 87 | try { 88 | const refreshedToken = await renewToken( 89 | this.apiClient, 90 | this._accessToken 91 | ); 92 | return this.sdkAuthenticator(refreshedToken.accessToken); 93 | } catch (err) { 94 | throw newInfisicalError(err); 95 | } 96 | }, 97 | }; 98 | 99 | /** 100 | * Gets the current access token that is set on the SDK instance 101 | * @returns The current access token or null if no access token is set. `null` is returned if the SDK is not authenticated. 102 | */ 103 | getAccessToken = () => this._accessToken || null; 104 | 105 | accessToken = (token: string) => { 106 | return this.sdkAuthenticator(token); 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { ApiClient } from "./api/base"; 2 | import { AuthApi } from "./api/endpoints/auth"; 3 | import { SecretsApi } from "./api/endpoints/secrets"; 4 | import { DynamicSecretsApi } from "./api/endpoints/dynamic-secrets"; 5 | import { EnvironmentsApi } from "./api/endpoints/environments"; 6 | import { ProjectsApi } from "./api/endpoints/projects"; 7 | import { FoldersApi } from "./api/endpoints/folders"; 8 | 9 | import SecretsClient from "./custom/secrets"; 10 | import AuthClient from "./custom/auth"; 11 | import DynamicSecretsClient from "./custom/dynamic-secrets"; 12 | import EnvironmentsClient from "./custom/environments"; 13 | import ProjectsClient from "./custom/projects"; 14 | import FoldersClient from "./custom/folders"; 15 | import { KmsApi } from "./api/endpoints/kms"; 16 | import KmsClient from "./custom/kms"; 17 | 18 | type InfisicalSDKOptions = { 19 | siteUrl?: string; 20 | }; 21 | 22 | class InfisicalSDK { 23 | private apiClient: ApiClient; 24 | 25 | // API instances 26 | private authApi: AuthApi; 27 | private secretsApi: SecretsApi; 28 | private dynamicSecretsApi: DynamicSecretsApi; 29 | private environmentsApi: EnvironmentsApi; 30 | private projectsApi: ProjectsApi; 31 | private foldersApi: FoldersApi; 32 | private kmsApi: KmsApi; 33 | 34 | // Domain clients 35 | private authClient: AuthClient; 36 | private secretsClient: SecretsClient; 37 | private dynamicSecretsClient: DynamicSecretsClient; 38 | private environmentsClient: EnvironmentsClient; 39 | private projectsClient: ProjectsClient; 40 | private foldersClient: FoldersClient; 41 | private kmsClient: KmsClient; 42 | 43 | constructor(options?: InfisicalSDKOptions) { 44 | const baseURL = options?.siteUrl || "https://app.infisical.com"; 45 | 46 | // Initialize the base API client 47 | this.apiClient = new ApiClient({ baseURL }); 48 | 49 | // Initialize API service instances 50 | this.authApi = new AuthApi(this.apiClient); 51 | this.secretsApi = new SecretsApi(this.apiClient); 52 | this.dynamicSecretsApi = new DynamicSecretsApi(this.apiClient); 53 | this.environmentsApi = new EnvironmentsApi(this.apiClient); 54 | this.projectsApi = new ProjectsApi(this.apiClient); 55 | this.foldersApi = new FoldersApi(this.apiClient); 56 | this.kmsApi = new KmsApi(this.apiClient); 57 | 58 | // Initialize domain clients 59 | this.authClient = new AuthClient(this.authenticate.bind(this), this.authApi); 60 | this.secretsClient = new SecretsClient(this.secretsApi); 61 | this.dynamicSecretsClient = new DynamicSecretsClient(this.dynamicSecretsApi); 62 | this.environmentsClient = new EnvironmentsClient(this.environmentsApi); 63 | this.projectsClient = new ProjectsClient(this.projectsApi); 64 | this.foldersClient = new FoldersClient(this.foldersApi); 65 | this.kmsClient = new KmsClient(this.kmsApi); 66 | } 67 | 68 | private authenticate(accessToken: string) { 69 | // Set the token on the API client 70 | this.apiClient.setAccessToken(accessToken); 71 | 72 | // Reinitialize the auth client with the token 73 | this.authClient = new AuthClient(this.authenticate.bind(this), this.authApi, accessToken); 74 | 75 | return this; 76 | } 77 | 78 | // Public methods to access domain clients 79 | secrets = () => this.secretsClient; 80 | environments = () => this.environmentsClient; 81 | projects = () => this.projectsClient; 82 | folders = () => this.foldersClient; 83 | dynamicSecrets = () => this.dynamicSecretsClient; 84 | auth = () => this.authClient; 85 | kms = () => this.kmsClient; 86 | } 87 | 88 | // Export main SDK class 89 | export { InfisicalSDK }; 90 | 91 | export * from "./api/types"; 92 | 93 | // Export types and enums from schemas 94 | export { TDynamicSecretProvider, DynamicSecretProviders, SqlProviders } from "./custom/schemas"; 95 | -------------------------------------------------------------------------------- /src/api/types/secrets.ts: -------------------------------------------------------------------------------- 1 | export enum SecretType { 2 | Shared = "shared", 3 | Personal = "personal" 4 | } 5 | 6 | export interface Secret { 7 | id: string; 8 | workspaceId: string; 9 | environment: string; 10 | secretKey: string; 11 | secretValue: string; 12 | secretComment?: string; 13 | secretPath?: string; 14 | secretValueHidden: boolean; 15 | secretReminderNote?: string; 16 | secretReminderRepeatDays?: number; 17 | skipMultilineEncoding?: boolean; 18 | folderId?: string; 19 | actor?: { 20 | actorId?: string; 21 | name?: string; 22 | actorType?: string; 23 | membershipId?: string; 24 | } 25 | isRotatedSecret: boolean; 26 | rotationId?: string; 27 | secretMetadata?: Record; 28 | type: SecretType; 29 | createdAt: string; 30 | updatedAt: string; 31 | version: number; 32 | tags: string[]; 33 | } 34 | 35 | export interface ListSecretsRequest { 36 | workspaceId: string; 37 | environment: string; 38 | expandSecretReferences?: string; 39 | include_imports?: string; 40 | recursive?: string; 41 | secretPath?: string; 42 | tagSlugs?: string; 43 | viewSecretValue?: string; 44 | } 45 | 46 | export interface ListSecretsResponse { 47 | secrets: Secret[]; 48 | imports?: Array<{ 49 | secretPath: string; 50 | secrets: Secret[]; 51 | folderId?: string; 52 | environment: string; 53 | }>; 54 | } 55 | 56 | export interface GetSecretRequest { 57 | secretName: string; 58 | workspaceId: string; 59 | environment: string; 60 | expandSecretReferences?: string; 61 | includeImports?: string; 62 | secretPath?: string; 63 | type?: SecretType; 64 | version?: number; 65 | viewSecretValue?: string; 66 | } 67 | 68 | export interface GetSecretResponse { 69 | secret: Secret; 70 | } 71 | 72 | export interface CreateSecretRequest { 73 | workspaceId: string; 74 | environment: string; 75 | secretValue: string; 76 | secretComment?: string; 77 | secretPath?: string; 78 | secretReminderNote?: string; 79 | secretReminderRepeatDays?: number; 80 | skipMultilineEncoding?: boolean; 81 | tagIds?: string[]; 82 | type?: SecretType; 83 | } 84 | 85 | export interface UpdateSecretRequest { 86 | workspaceId: string; 87 | environment: string; 88 | secretValue?: string; 89 | newSecretName?: string; 90 | secretComment?: string; 91 | secretPath?: string; 92 | secretReminderNote?: string; 93 | secretReminderRepeatDays?: number; 94 | skipMultilineEncoding?: boolean; 95 | tagIds?: string[]; 96 | type?: SecretType; 97 | metadata?: Record; 98 | } 99 | 100 | export interface DeleteSecretRequest { 101 | workspaceId: string; 102 | environment: string; 103 | secretPath?: string; 104 | type?: SecretType; 105 | } 106 | 107 | export type ListSecretsOptions = { 108 | environment: string; 109 | projectId: string; 110 | expandSecretReferences?: boolean; 111 | attachToProcessEnv?: boolean; 112 | includeImports?: boolean; 113 | recursive?: boolean; 114 | secretPath?: string; 115 | tagSlugs?: string[]; 116 | viewSecretValue?: boolean; 117 | }; 118 | 119 | export type GetSecretOptions = { 120 | environment: string; 121 | secretName: string; 122 | expandSecretReferences?: boolean; 123 | includeImports?: boolean; 124 | secretPath?: string; 125 | type?: SecretType; 126 | version?: number; 127 | projectId: string; 128 | viewSecretValue?: boolean; 129 | }; 130 | 131 | export type BaseSecretOptions = { 132 | environment: string; 133 | projectId: string; 134 | secretComment?: string; 135 | secretPath?: string; 136 | secretReminderNote?: string; 137 | secretReminderRepeatDays?: number; 138 | skipMultilineEncoding?: boolean; 139 | tagIds?: string[]; 140 | type?: SecretType; 141 | metadata?: Record; 142 | secretMetadata?: Record[]; 143 | }; 144 | 145 | export type UpdateSecretOptions = { 146 | secretValue?: string; 147 | newSecretName?: string; 148 | } & BaseSecretOptions; 149 | 150 | export type CreateSecretOptions = { 151 | secretValue: string; 152 | } & BaseSecretOptions; 153 | 154 | export type DeleteSecretOptions = { 155 | environment: string; 156 | projectId: string; 157 | secretPath?: string; 158 | type?: SecretType; 159 | }; 160 | -------------------------------------------------------------------------------- /test/test-kms.ts: -------------------------------------------------------------------------------- 1 | import { InfisicalSDK } from "../src"; 2 | import { EncryptionAlgorithm, KeyUsage, KmsKey } from "../src/api/types/kms"; 3 | 4 | (async () => { 5 | const client = new InfisicalSDK({ 6 | siteUrl: "https://app.infisical.com" // Optional, defaults to https://app.infisical.com 7 | }); 8 | 9 | const universalAuthClientId = process.env.UNIVERSAL_AUTH_CLIENT_ID; 10 | const universalAuthClientSecret = process.env.UNIVERSAL_AUTH_CLIENT_SECRET; 11 | const projectId = process.env.PROJECT_ID; 12 | 13 | if (!universalAuthClientId || !universalAuthClientSecret) { 14 | throw new Error("UNIVERSAL_AUTH_CLIENT_ID and UNIVERSAL_AUTH_CLIENT_SECRET must be set"); 15 | } 16 | 17 | if (!projectId) { 18 | throw new Error("PROJECT_ID must be set"); 19 | } 20 | 21 | console.log("Logging in"); 22 | 23 | await client.auth().universalAuth.login({ 24 | clientId: universalAuthClientId, 25 | clientSecret: universalAuthClientSecret 26 | }); 27 | console.log("Logged in"); 28 | 29 | console.log("Creating keys"); 30 | 31 | const keysToCreate = [ 32 | { 33 | name: "test-aes-256-gcm", 34 | keyUsage: KeyUsage.ENCRYPTION, 35 | encryptionAlgorithm: EncryptionAlgorithm.AES_256_GCM 36 | }, 37 | { 38 | name: "test-aes-128-gcm", 39 | keyUsage: KeyUsage.ENCRYPTION, 40 | encryptionAlgorithm: EncryptionAlgorithm.AES_128_GCM 41 | }, 42 | { 43 | name: "test-ecc-nist-p256", 44 | keyUsage: KeyUsage.SIGNING, 45 | encryptionAlgorithm: EncryptionAlgorithm.ECC_NIST_P256 46 | }, 47 | { 48 | name: "test-rsa-4096", 49 | keyUsage: KeyUsage.SIGNING, 50 | encryptionAlgorithm: EncryptionAlgorithm.RSA_4096 51 | } 52 | ] as const; 53 | 54 | console.log("Creating keys", keysToCreate); 55 | 56 | const createdKeys: KmsKey[] = []; 57 | 58 | // Create all the keys 59 | for (const key of keysToCreate) { 60 | const createdKey = await client.kms().keys().create({ 61 | projectId, 62 | description: key.name, 63 | encryptionAlgorithm: key.encryptionAlgorithm, 64 | keyUsage: key.keyUsage, 65 | name: key.name 66 | }); 67 | console.log("Created key", createdKey.name); 68 | createdKeys.push(createdKey); 69 | } 70 | 71 | // Get all the keys by name 72 | for (const createdKey of createdKeys) { 73 | const key = await client.kms().keys().getByName({ 74 | projectId: createdKey.projectId, 75 | name: createdKey.name 76 | }); 77 | 78 | console.log(key); 79 | console.log("Got key by name", key.name); 80 | } 81 | 82 | // Encrypt / decrypt data with encryption keys 83 | 84 | for (const createdKey of createdKeys) { 85 | if (createdKey.keyUsage !== KeyUsage.ENCRYPTION) { 86 | console.log("Skipping key for encryption mode:", createdKey.name); 87 | continue; 88 | } 89 | 90 | const encryptedData = await client 91 | .kms() 92 | .encryption() 93 | .encrypt({ 94 | keyId: createdKey.id, 95 | plaintext: Buffer.from("test data").toString("base64") 96 | }); 97 | 98 | const decryptedData = await client.kms().encryption().decrypt({ 99 | keyId: createdKey.id, 100 | ciphertext: encryptedData 101 | }); 102 | 103 | console.log("Encrypted data:", { 104 | raw: encryptedData 105 | }); 106 | console.log("Decrypted data:", { 107 | raw: decryptedData, 108 | decoded: Buffer.from(decryptedData, "base64").toString("utf-8") 109 | }); 110 | } 111 | 112 | // Sign / verify data with signing keys 113 | for (const createdKey of createdKeys) { 114 | if (createdKey.keyUsage !== KeyUsage.SIGNING) { 115 | console.log("Skipping key for signing mode:", createdKey.name); 116 | continue; 117 | } 118 | 119 | const testData = Buffer.from("some test data to sign").toString("base64"); 120 | 121 | const publicKey = await client.kms().signing().getPublicKey({ 122 | keyId: createdKey.id 123 | }); 124 | console.log(`Public key for key ${createdKey.name}:`, publicKey); 125 | 126 | const signingAlgorithms = await client.kms().signing().listSigningAlgorithms({ 127 | keyId: createdKey.id 128 | }); 129 | 130 | console.log(`Signing algorithms for key ${createdKey.name}:`, signingAlgorithms); 131 | 132 | for (const signingAlgorithm of signingAlgorithms) { 133 | const signedData = await client.kms().signing().sign({ 134 | keyId: createdKey.id, 135 | data: testData, 136 | signingAlgorithm: signingAlgorithm 137 | }); 138 | 139 | console.log("Signed data:", signedData); 140 | const verifiedData = await client.kms().signing().verify({ 141 | keyId: createdKey.id, 142 | data: testData, 143 | signature: signedData.signature, 144 | signingAlgorithm: signingAlgorithm 145 | }); 146 | console.log("Verified data:", verifiedData); 147 | } 148 | } 149 | 150 | // Delete all the keys 151 | for (const createdKey of createdKeys) { 152 | await client.kms().keys().delete({ 153 | keyId: createdKey.id 154 | }); 155 | console.log("Deleted key", createdKey.name); 156 | } 157 | })(); 158 | -------------------------------------------------------------------------------- /src/custom/secrets.ts: -------------------------------------------------------------------------------- 1 | import { SecretsApi } from "../api/endpoints/secrets"; 2 | import { newInfisicalError } from "./errors"; 3 | import { ListSecretsOptions, GetSecretOptions, UpdateSecretOptions, CreateSecretOptions, DeleteSecretOptions } from "../api/types/secrets"; 4 | 5 | const convertBool = (value?: boolean) => (value ? "true" : "false"); 6 | 7 | const defaultBoolean = (value?: boolean, defaultValue: boolean = false) => { 8 | if (value === undefined) { 9 | return defaultValue; 10 | } 11 | return value; 12 | }; 13 | 14 | export default class SecretsClient { 15 | constructor(private apiClient: SecretsApi) {} 16 | 17 | listSecrets = async (options: ListSecretsOptions) => { 18 | try { 19 | const res = await this.apiClient.listSecrets({ 20 | workspaceId: options.projectId, 21 | environment: options.environment, 22 | expandSecretReferences: convertBool(defaultBoolean(options.expandSecretReferences, true)), 23 | include_imports: convertBool(options.includeImports), 24 | recursive: convertBool(options.recursive), 25 | secretPath: options.secretPath, 26 | tagSlugs: options.tagSlugs ? options.tagSlugs.join(",") : undefined, 27 | viewSecretValue: convertBool(options.viewSecretValue ?? true) 28 | }); 29 | 30 | if (options.attachToProcessEnv) { 31 | let includedSecrets = res.secrets; 32 | if (res.imports?.length) { 33 | for (const imp of res.imports) { 34 | for (const importSecret of imp.secrets) { 35 | if (!includedSecrets.find(includedSecret => includedSecret.secretKey === importSecret.secretKey)) { 36 | includedSecrets.push(importSecret); 37 | } 38 | } 39 | } 40 | } 41 | 42 | for (const secret of includedSecrets) { 43 | process.env[secret.secretKey] = secret.secretValue; 44 | } 45 | } 46 | 47 | return res; 48 | } catch (err) { 49 | throw newInfisicalError(err); 50 | } 51 | }; 52 | 53 | listSecretsWithImports = async ( 54 | options: Omit 55 | ) => { 56 | const res = await this.listSecrets({ 57 | ...options, 58 | includeImports: true, 59 | }); 60 | 61 | let { imports, secrets } = res; 62 | if (imports) { 63 | for (const imp of imports) { 64 | for (const importedSecret of imp.secrets) { 65 | // CASE: We need to ensure that the imported values don't override the "base" secrets. 66 | // Priority order is: 67 | // Local/Preset variables -> Actual secrets -> Imported secrets (high->low) 68 | 69 | // Check if the secret already exists in the secrets list 70 | if (!secrets.find((s) => s.secretKey === importedSecret.secretKey)) { 71 | secrets.push({ 72 | ...importedSecret, 73 | secretPath: imp.secretPath, 74 | createdAt: new Date().toISOString(), 75 | updatedAt: new Date().toISOString(), 76 | tags: [], 77 | }); 78 | } 79 | } 80 | } 81 | } 82 | 83 | return secrets; 84 | }; 85 | 86 | getSecret = async (options: GetSecretOptions) => { 87 | try { 88 | const res = await this.apiClient.getSecret({ 89 | secretName: options.secretName, 90 | workspaceId: options.projectId, 91 | environment: options.environment, 92 | expandSecretReferences: convertBool( 93 | defaultBoolean(options.expandSecretReferences, true) 94 | ), 95 | includeImports: convertBool(options.includeImports), 96 | secretPath: options.secretPath, 97 | type: options.type, 98 | version: options.version, 99 | viewSecretValue: convertBool(options.viewSecretValue ?? true), 100 | }); 101 | return res.secret; 102 | } catch (err) { 103 | throw newInfisicalError(err); 104 | } 105 | }; 106 | 107 | updateSecret = async (secretName: string, options: UpdateSecretOptions) => { 108 | try { 109 | return await this.apiClient.updateSecret(secretName, { 110 | workspaceId: options.projectId, 111 | environment: options.environment, 112 | secretValue: options.secretValue, 113 | newSecretName: options.newSecretName, 114 | secretComment: options.secretComment, 115 | secretPath: options.secretPath, 116 | secretReminderNote: options.secretReminderNote, 117 | secretReminderRepeatDays: options.secretReminderRepeatDays, 118 | skipMultilineEncoding: options.skipMultilineEncoding, 119 | tagIds: options.tagIds, 120 | type: options.type, 121 | metadata: options.metadata, 122 | }); 123 | } catch (err) { 124 | throw newInfisicalError(err); 125 | } 126 | }; 127 | 128 | createSecret = async (secretName: string, options: CreateSecretOptions) => { 129 | try { 130 | return await this.apiClient.createSecret(secretName, { 131 | workspaceId: options.projectId, 132 | environment: options.environment, 133 | secretValue: options.secretValue, 134 | secretComment: options.secretComment, 135 | secretPath: options.secretPath, 136 | secretReminderNote: options.secretReminderNote, 137 | secretReminderRepeatDays: options.secretReminderRepeatDays, 138 | skipMultilineEncoding: options.skipMultilineEncoding, 139 | tagIds: options.tagIds, 140 | type: options.type, 141 | }); 142 | } catch (err) { 143 | throw newInfisicalError(err); 144 | } 145 | }; 146 | 147 | deleteSecret = async (secretName: string, options: DeleteSecretOptions) => { 148 | try { 149 | return await this.apiClient.deleteSecret(secretName, { 150 | workspaceId: options.projectId, 151 | environment: options.environment, 152 | secretPath: options.secretPath, 153 | type: options.type, 154 | }); 155 | } catch (err) { 156 | throw newInfisicalError(err); 157 | } 158 | }; 159 | } 160 | -------------------------------------------------------------------------------- /img/logoname-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 63 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 64 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 65 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 66 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 67 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 68 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 69 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 70 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 71 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 72 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 73 | 74 | /* Interop Constraints */ 75 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 76 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 77 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 78 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 79 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 80 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 81 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 82 | 83 | /* Type Checking */ 84 | "strict": true, /* Enable all strict type-checking options. */ 85 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 86 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 87 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 88 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 89 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 90 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 91 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 92 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 93 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 94 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 95 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 96 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 97 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 98 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 99 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 100 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 101 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 102 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 103 | 104 | /* Completeness */ 105 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 106 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/custom/schemas/dynamic-secrets.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export enum SqlProviders { 4 | Postgres = "postgres", 5 | MySQL = "mysql2", 6 | Oracle = "oracledb", 7 | MsSQL = "mssql", 8 | SapAse = "sap-ase", 9 | } 10 | 11 | export enum ElasticSearchAuthTypes { 12 | User = "user", 13 | ApiKey = "api-key", 14 | } 15 | 16 | export enum LdapCredentialType { 17 | Dynamic = "dynamic", 18 | Static = "static", 19 | } 20 | 21 | export enum TotpConfigType { 22 | URL = "url", 23 | MANUAL = "manual", 24 | } 25 | 26 | export enum TotpAlgorithm { 27 | SHA1 = "sha1", 28 | SHA256 = "sha256", 29 | SHA512 = "sha512", 30 | } 31 | 32 | const passwordRequirementsSchema = z.object({ 33 | length: z 34 | .number() 35 | .min(1, { message: "Password length must be at least 1" }) 36 | .max(250, { message: "Password length must be at most 250" }), 37 | 38 | required: z.object({ 39 | minUppercase: z.number().min(0).optional(), 40 | minLowercase: z.number().min(0).optional(), 41 | minDigits: z.number().min(0).optional(), 42 | minSymbols: z.number().min(0).optional(), 43 | }), 44 | allowedSymbols: z.string().optional(), 45 | }); 46 | 47 | const DynamicSecretRedisDBSchema = z.object({ 48 | host: z.string().trim().toLowerCase(), 49 | port: z.number(), 50 | username: z.string().trim(), // this is often "default". 51 | password: z.string().trim().optional(), 52 | creationStatement: z.string().trim(), 53 | revocationStatement: z.string().trim(), 54 | renewStatement: z.string().trim().optional(), 55 | ca: z.string().optional(), 56 | }); 57 | 58 | const DynamicSecretAwsElastiCacheSchema = z.object({ 59 | clusterName: z.string().trim().min(1), 60 | accessKeyId: z.string().trim().min(1), 61 | secretAccessKey: z.string().trim().min(1), 62 | 63 | region: z.string().trim(), 64 | creationStatement: z.string().trim(), 65 | revocationStatement: z.string().trim(), 66 | ca: z.string().optional(), 67 | }); 68 | 69 | const DynamicSecretElasticSearchSchema = z.object({ 70 | host: z.string().trim().min(1), 71 | port: z.number(), 72 | roles: z.array(z.string().trim().min(1)).min(1), 73 | 74 | // two auth types "user, apikey" 75 | auth: z.discriminatedUnion("type", [ 76 | z.object({ 77 | type: z.literal(ElasticSearchAuthTypes.User), 78 | username: z.string().trim(), 79 | password: z.string().trim(), 80 | }), 81 | z.object({ 82 | type: z.literal(ElasticSearchAuthTypes.ApiKey), 83 | apiKey: z.string().trim(), 84 | apiKeyId: z.string().trim(), 85 | }), 86 | ]), 87 | 88 | ca: z.string().optional(), 89 | }); 90 | 91 | const DynamicSecretRabbitMqSchema = z.object({ 92 | host: z.string().trim().min(1), 93 | port: z.number(), 94 | tags: z.array(z.string().trim()).default([]), 95 | 96 | username: z.string().trim().min(1), 97 | password: z.string().trim().min(1), 98 | 99 | ca: z.string().optional(), 100 | 101 | virtualHost: z.object({ 102 | name: z.string().trim().min(1), 103 | permissions: z.object({ 104 | read: z.string().trim().min(1), 105 | write: z.string().trim().min(1), 106 | configure: z.string().trim().min(1), 107 | }), 108 | }), 109 | }); 110 | 111 | const DynamicSecretSqlDBSchema = z.object({ 112 | client: z.nativeEnum(SqlProviders), 113 | host: z.string().trim().toLowerCase(), 114 | port: z.number(), 115 | database: z.string().trim(), 116 | username: z.string().trim(), 117 | password: z.string().trim(), 118 | creationStatement: z.string().trim(), 119 | revocationStatement: z.string().trim(), 120 | renewStatement: z.string().trim().optional(), 121 | ca: z.string().optional(), 122 | passwordRequirements: passwordRequirementsSchema.optional(), 123 | }); 124 | 125 | const DynamicSecretCassandraSchema = z.object({ 126 | host: z.string().trim().toLowerCase(), 127 | port: z.number(), 128 | localDataCenter: z.string().trim().min(1), 129 | keyspace: z.string().trim().optional(), 130 | username: z.string().trim(), 131 | password: z.string().trim(), 132 | creationStatement: z.string().trim(), 133 | revocationStatement: z.string().trim(), 134 | renewStatement: z.string().trim().optional(), 135 | ca: z.string().optional(), 136 | }); 137 | 138 | const DynamicSecretSapAseSchema = z.object({ 139 | host: z.string().trim().toLowerCase(), 140 | port: z.number(), 141 | database: z.string().trim(), 142 | username: z.string().trim(), 143 | password: z.string().trim(), 144 | creationStatement: z.string().trim(), 145 | revocationStatement: z.string().trim(), 146 | }); 147 | 148 | const DynamicSecretAwsIamSchema = z.object({ 149 | accessKey: z.string().trim().min(1), 150 | secretAccessKey: z.string().trim().min(1), 151 | region: z.string().trim().min(1), 152 | awsPath: z.string().trim().optional(), 153 | permissionBoundaryPolicyArn: z.string().trim().optional(), 154 | policyDocument: z.string().trim().optional(), 155 | userGroups: z.string().trim().optional(), 156 | policyArns: z.string().trim().optional(), 157 | }); 158 | 159 | const DynamicSecretMongoAtlasSchema = z.object({ 160 | adminPublicKey: z 161 | .string() 162 | .trim() 163 | .min(1) 164 | .describe("Admin user public api key"), 165 | adminPrivateKey: z 166 | .string() 167 | .trim() 168 | .min(1) 169 | .describe("Admin user private api key"), 170 | groupId: z 171 | .string() 172 | .trim() 173 | .min(1) 174 | .describe( 175 | "Unique 24-hexadecimal digit string that identifies your project. This is same as project id" 176 | ), 177 | roles: z 178 | .object({ 179 | collectionName: z 180 | .string() 181 | .optional() 182 | .describe("Collection on which this role applies."), 183 | databaseName: z 184 | .string() 185 | .min(1) 186 | .describe("Database to which the user is granted access privileges."), 187 | roleName: z 188 | .string() 189 | .min(1) 190 | .describe( 191 | ' 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.' 192 | ), 193 | }) 194 | .array() 195 | .min(1), 196 | scopes: z 197 | .object({ 198 | name: z 199 | .string() 200 | .min(1) 201 | .describe( 202 | "Human-readable label that identifies the cluster or MongoDB Atlas Data Lake that this database user can access." 203 | ), 204 | type: z 205 | .string() 206 | .min(1) 207 | .describe( 208 | "Category of resource that this database user can access. Enum: CLUSTER, DATA_LAKE, STREAM" 209 | ), 210 | }) 211 | .array(), 212 | }); 213 | 214 | const DynamicSecretMongoDBSchema = z.object({ 215 | host: z.string().min(1).trim().toLowerCase(), 216 | port: z.number().optional(), 217 | username: z.string().min(1).trim(), 218 | password: z.string().min(1).trim(), 219 | database: z.string().min(1).trim(), 220 | ca: z.string().min(1).optional(), 221 | roles: z 222 | .string() 223 | .array() 224 | .min(1) 225 | .describe( 226 | '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.' 227 | ), 228 | }); 229 | 230 | const DynamicSecretSapHanaSchema = z.object({ 231 | host: z.string().trim().toLowerCase(), 232 | port: z.number(), 233 | username: z.string().trim(), 234 | password: z.string().trim(), 235 | creationStatement: z.string().trim(), 236 | revocationStatement: z.string().trim(), 237 | renewStatement: z.string().trim().optional(), 238 | ca: z.string().optional(), 239 | }); 240 | 241 | const DynamicSecretSnowflakeSchema = z.object({ 242 | accountId: z.string().trim().min(1), 243 | orgId: z.string().trim().min(1), 244 | username: z.string().trim().min(1), 245 | password: z.string().trim().min(1), 246 | creationStatement: z.string().trim().min(1), 247 | revocationStatement: z.string().trim().min(1), 248 | renewStatement: z.string().trim().optional(), 249 | }); 250 | 251 | const AzureEntraIDSchema = z.object({ 252 | tenantId: z.string().trim().min(1), 253 | userId: z.string().trim().min(1), 254 | email: z.string().trim().min(1), 255 | applicationId: z.string().trim().min(1), 256 | clientSecret: z.string().trim().min(1), 257 | }); 258 | 259 | const LdapSchema = z.union([ 260 | z.object({ 261 | url: z.string().trim().min(1), 262 | binddn: z.string().trim().min(1), 263 | bindpass: z.string().trim().min(1), 264 | ca: z.string().optional(), 265 | credentialType: z 266 | .literal(LdapCredentialType.Dynamic) 267 | .optional() 268 | .default(LdapCredentialType.Dynamic), 269 | creationLdif: z.string().min(1), 270 | revocationLdif: z.string().min(1), 271 | rollbackLdif: z.string().optional(), 272 | }), 273 | z.object({ 274 | url: z.string().trim().min(1), 275 | binddn: z.string().trim().min(1), 276 | bindpass: z.string().trim().min(1), 277 | ca: z.string().optional(), 278 | credentialType: z.literal(LdapCredentialType.Static), 279 | rotationLdif: z.string().min(1), 280 | }), 281 | ]); 282 | 283 | const DynamicSecretTotpSchema = z.discriminatedUnion("configType", [ 284 | z.object({ 285 | configType: z.literal(TotpConfigType.URL), 286 | url: z 287 | .string() 288 | .url() 289 | .trim() 290 | .min(1) 291 | .refine((val) => { 292 | const urlObj = new URL(val); 293 | const secret = urlObj.searchParams.get("secret"); 294 | 295 | return Boolean(secret); 296 | }, "OTP URL must contain secret field"), 297 | }), 298 | z.object({ 299 | configType: z.literal(TotpConfigType.MANUAL), 300 | secret: z 301 | .string() 302 | .trim() 303 | .min(1) 304 | .transform((val) => val.replace(/\s+/g, "")), 305 | period: z.number().optional(), 306 | algorithm: z.nativeEnum(TotpAlgorithm).optional(), 307 | digits: z.number().optional(), 308 | }), 309 | ]); 310 | 311 | export enum DynamicSecretProviders { 312 | SqlDatabase = "sql-database", 313 | Cassandra = "cassandra", 314 | AwsIam = "aws-iam", 315 | Redis = "redis", 316 | AwsElastiCache = "aws-elasticache", 317 | MongoAtlas = "mongo-db-atlas", 318 | ElasticSearch = "elastic-search", 319 | MongoDB = "mongo-db", 320 | RabbitMq = "rabbit-mq", 321 | AzureEntraID = "azure-entra-id", 322 | Ldap = "ldap", 323 | SapHana = "sap-hana", 324 | Snowflake = "snowflake", 325 | Totp = "totp", 326 | SapAse = "sap-ase", 327 | } 328 | 329 | const DynamicSecretProviderSchema = z.discriminatedUnion("type", [ 330 | z.object({ 331 | type: z.literal(DynamicSecretProviders.SqlDatabase), 332 | inputs: DynamicSecretSqlDBSchema, 333 | }), 334 | z.object({ 335 | type: z.literal(DynamicSecretProviders.Cassandra), 336 | inputs: DynamicSecretCassandraSchema, 337 | }), 338 | z.object({ 339 | type: z.literal(DynamicSecretProviders.SapAse), 340 | inputs: DynamicSecretSapAseSchema, 341 | }), 342 | z.object({ 343 | type: z.literal(DynamicSecretProviders.AwsIam), 344 | inputs: DynamicSecretAwsIamSchema, 345 | }), 346 | z.object({ 347 | type: z.literal(DynamicSecretProviders.Redis), 348 | inputs: DynamicSecretRedisDBSchema, 349 | }), 350 | z.object({ 351 | type: z.literal(DynamicSecretProviders.SapHana), 352 | inputs: DynamicSecretSapHanaSchema, 353 | }), 354 | z.object({ 355 | type: z.literal(DynamicSecretProviders.AwsElastiCache), 356 | inputs: DynamicSecretAwsElastiCacheSchema, 357 | }), 358 | z.object({ 359 | type: z.literal(DynamicSecretProviders.MongoAtlas), 360 | inputs: DynamicSecretMongoAtlasSchema, 361 | }), 362 | z.object({ 363 | type: z.literal(DynamicSecretProviders.ElasticSearch), 364 | inputs: DynamicSecretElasticSearchSchema, 365 | }), 366 | z.object({ 367 | type: z.literal(DynamicSecretProviders.MongoDB), 368 | inputs: DynamicSecretMongoDBSchema, 369 | }), 370 | z.object({ 371 | type: z.literal(DynamicSecretProviders.RabbitMq), 372 | inputs: DynamicSecretRabbitMqSchema, 373 | }), 374 | z.object({ 375 | type: z.literal(DynamicSecretProviders.AzureEntraID), 376 | inputs: AzureEntraIDSchema, 377 | }), 378 | z.object({ 379 | type: z.literal(DynamicSecretProviders.Ldap), 380 | inputs: LdapSchema, 381 | }), 382 | z.object({ 383 | type: z.literal(DynamicSecretProviders.Snowflake), 384 | inputs: DynamicSecretSnowflakeSchema, 385 | }), 386 | z.object({ 387 | type: z.literal(DynamicSecretProviders.Totp), 388 | inputs: DynamicSecretTotpSchema, 389 | }), 390 | ]); 391 | 392 | export type TDynamicSecretProvider = z.infer< 393 | typeof DynamicSecretProviderSchema 394 | >; 395 | --------------------------------------------------------------------------------