Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: renewal & better errors #4

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ npm install @infisical/sdk
import { InfisicalSDK } from '@infisical/sdk'

const client = new InfisicalSDK({
siteUrl: "your-infisical-instance.com" // Optional, defaults to https://app.infisical.com
siteUrl: "your-infisical-instance.com", // Optional, defaults to https://app.infisical.com
autoTokenRefresh: false // Optional, defaults to true
});

// Authenticate with Infisical
Expand All @@ -31,6 +32,18 @@ const allSecrets = await client.secrets().listSecrets({
console.log("Fetched secrets", allSecrets)
```

#### SDK Initialization
```typescript
const client = new InfisicalSDK({
siteUrl: "your-infisical-instance.com",
autoTokenRefresh: false
});
```
**Parameters:**
- `options` (object):
- `siteUrl` (string, optional): Optionally provide a URL to your own Infisical instance. Defaults to `https://app.infisical.com`
- `autoTokenRefresh` (boolean, optional): Whether to automatically refresh the access token when it expires. Defaults to `true`.

## Core Methods

The SDK methods are organized into the following high-level categories:
Expand All @@ -44,6 +57,7 @@ The `Auth` component provides methods for authentication:

#### Universal Auth

#### Authenticating
```typescript
await client.auth().universalAuth.login({
clientId: "<machine-identity-client-id>",
Expand All @@ -56,6 +70,12 @@ await client.auth().universalAuth.login({
- `clientId` (string): The client ID of your Machine Identity.
- `clientSecret` (string): The client secret of your Machine Identity.

#### Renewing
You can renew the authentication token that is currently set by using the `renew()` method.
```typescript
await client.auth().universalAuth.renew();
```


#### Manually set access token
By default, when you run a successful `.login()` method call, the access token returned will be auto set for the client instance. However, if you wish to set the access token manually, you may use this method.
Expand All @@ -73,6 +93,7 @@ client.auth().accessToken("<your-access-token>")
> [!NOTE]
> AWS IAM auth only works when the SDK is being used from within an AWS service, such as Lambda, EC2, etc.

#### Authenticating
```typescript
await client.auth().awsIamAuth.login({
identityId: "<your-identity-id>"
Expand All @@ -83,6 +104,12 @@ await client.auth().awsIamAuth.login({
- `options` (object):
- `identityId` (string): The ID of your identity

#### Renewing
You can renew the authentication token that is currently set by using the `renew()` method.
```typescript
await client.auth().awsIamAuth.renew();
```


### `secrets`

Expand Down
126 changes: 100 additions & 26 deletions src/custom/auth.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,134 @@
import { InfisicalSDK } from "..";
import { ApiV1AuthUniversalAuthLoginPostRequest } from "../infisicalapi_client";
import { DefaultApi as InfisicalApi } from "../infisicalapi_client";
import { AuthMethod, TAuthCredentials, TTokenDetails } from "../types";
import { MACHINE_IDENTITY_ID_ENV_NAME } from "./constants";
import { InfisicalError, newInfisicalError } from "./errors";
import { getAwsRegion, performAwsIamLogin } from "./util";

type AuthenticatorFunction = (accessToken: string) => InfisicalSDK;
type TAuthenticator = {
authenticate: (tokenDetails: Omit<TTokenDetails, "fetchedTime" | "firstFetchTime">) => Promise<InfisicalSDK>;
setCredentials: (credentials: TAuthCredentials) => void;
};

type AwsAuthLoginOptions = {
identityId?: string;
};

export const renewToken = async (apiClient: InfisicalApi, token?: string) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Add JSDoc comment to explain the purpose, parameters, return type, and possible errors of the function. This is helpful for maintainability and for other developers who might use this function.
'''
/**

  • Renews the access token using the provided Infisical API client.
  • @param apiClient - An instance of InfisicalApi to make the request.
  • @param token - The current access token to be renewed.
  • @returns The renewed token data.
  • @throws {InfisicalError} If the token is missing or the renewal request fails.
    */
    '''

try {
if (!token) {
throw new InfisicalError("Unable to renew access token, no access token set. Are you sure you're authenticated?");
}

const res = await apiClient.apiV1AuthTokenRenewPost({
apiV1AuthTokenRenewPostRequest: {
accessToken: token
}
});

return res.data;
} catch (err) {
throw newInfisicalError(err);
}
};

export default class AuthClient {
#sdkAuthenticator: AuthenticatorFunction;
#authenticator: TAuthenticator;
#apiClient: InfisicalApi;
#baseUrl: string;
#accessToken: string | undefined;
#credentials: TAuthCredentials | undefined;

constructor(authenticator: AuthenticatorFunction, apiInstance: InfisicalApi, baseUrl: string) {
this.#sdkAuthenticator = authenticator;
constructor(authenticator: TAuthenticator, apiInstance: InfisicalApi, accessToken?: string) {
this.#authenticator = authenticator;
this.#apiClient = apiInstance;
this.#baseUrl = baseUrl;
this.#accessToken = accessToken;
}

awsIamAuth = {
login: async (options?: AwsAuthLoginOptions) => {
const identityId = options?.identityId || process.env[MACHINE_IDENTITY_ID_ENV_NAME];
try {
const identityId = options?.identityId || process.env[MACHINE_IDENTITY_ID_ENV_NAME];

if (!identityId) {
throw new Error("Identity ID is required for AWS IAM authentication");
}
if (!identityId) {
throw new InfisicalError("Identity ID is required for AWS IAM authentication");
}

const iamRequest = await performAwsIamLogin(await getAwsRegion());
const iamRequest = await performAwsIamLogin(await getAwsRegion());

const res = await this.#apiClient.apiV1AuthAwsAuthLoginPost({
apiV1AuthAwsAuthLoginPostRequest: {
iamHttpRequestMethod: iamRequest.iamHttpRequestMethod,
iamRequestBody: Buffer.from(iamRequest.iamRequestBody).toString("base64"),
iamRequestHeaders: Buffer.from(JSON.stringify(iamRequest.iamRequestHeaders)).toString("base64"),
identityId
}
});
const res = await this.#apiClient.apiV1AuthAwsAuthLoginPost({
apiV1AuthAwsAuthLoginPostRequest: {
iamHttpRequestMethod: iamRequest.iamHttpRequestMethod,
iamRequestBody: Buffer.from(iamRequest.iamRequestBody).toString("base64"),
iamRequestHeaders: Buffer.from(JSON.stringify(iamRequest.iamRequestHeaders)).toString("base64"),
identityId
}
});

this.#authenticator.setCredentials({
type: AuthMethod.AWSIam,
credentials: {
identityId
}
});
return this.#authenticator.authenticate(res.data);
} catch (err) {
throw newInfisicalError(err);
}
},

return this.#sdkAuthenticator(res.data.accessToken);
renew: async () => {
try {
const refreshedToken = await renewToken(this.#apiClient, this.#accessToken);
return this.#authenticator.authenticate(refreshedToken);
} catch (err) {
throw newInfisicalError(err);
}
}
};

universalAuth = {
login: async (options: ApiV1AuthUniversalAuthLoginPostRequest) => {
const res = await this.#apiClient.apiV1AuthUniversalAuthLoginPost({
apiV1AuthUniversalAuthLoginPostRequest: options
});
try {
const res = await this.#apiClient.apiV1AuthUniversalAuthLoginPost({
apiV1AuthUniversalAuthLoginPostRequest: options
});

return this.#sdkAuthenticator(res.data.accessToken);
this.#authenticator.setCredentials({
type: AuthMethod.UniversalAuth,
credentials: {
clientId: options.clientId,
clientSecret: options.clientSecret
}
});
return this.#authenticator.authenticate(res.data);
} catch (err) {
throw newInfisicalError(err);
}
},

renew: async () => {
try {
const refreshedToken = await renewToken(this.#apiClient, this.#accessToken);
return this.#authenticator.authenticate(refreshedToken);
} catch (err) {
throw newInfisicalError(err);
}
}
};

accessToken = (token: string) => {
return this.#sdkAuthenticator(token);
accessToken = async (token: string) => {
try {
const tokenData = await renewToken(this.#apiClient, token);
this.#authenticator.setCredentials({
type: AuthMethod.AccessToken,
credentials: {
accessToken: token
}
});
return this.#authenticator.authenticate(tokenData);
} catch (err) {
throw newInfisicalError(err);
}
};
}
97 changes: 59 additions & 38 deletions src/custom/dynamic-secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
} from "../infisicalapi_client";

import type { TDynamicSecretProvider } from "./schemas/dynamic-secrets";
import { newInfisicalError } from "./errors";

type CreateDynamicSecretOptions = Omit<DefaultApiApiV1DynamicSecretsPostRequest["apiV1DynamicSecretsPostRequest"], "provider"> & {
provider: TDynamicSecretProvider;
Expand All @@ -23,67 +24,87 @@ export default class DynamicSecretsClient {
}

async create(options: CreateDynamicSecretOptions) {
const res = await this.#apiInstance.apiV1DynamicSecretsPost(
{
apiV1DynamicSecretsPostRequest: options as DefaultApiApiV1DynamicSecretsPostRequest["apiV1DynamicSecretsPostRequest"]
},
this.#requestOptions
);
try {
const res = await this.#apiInstance.apiV1DynamicSecretsPost(
{
apiV1DynamicSecretsPostRequest: options as DefaultApiApiV1DynamicSecretsPostRequest["apiV1DynamicSecretsPostRequest"]
},
this.#requestOptions
);

return res.data.dynamicSecret;
return res.data.dynamicSecret;
} catch (err) {
throw newInfisicalError(err);
}
}

async delete(dynamicSecretName: string, options: DefaultApiApiV1DynamicSecretsNameDeleteRequest["apiV1DynamicSecretsNameDeleteRequest"]) {
const res = await this.#apiInstance.apiV1DynamicSecretsNameDelete(
{
name: dynamicSecretName,
apiV1DynamicSecretsNameDeleteRequest: options
},
this.#requestOptions
);
try {
const res = await this.#apiInstance.apiV1DynamicSecretsNameDelete(
{
name: dynamicSecretName,
apiV1DynamicSecretsNameDeleteRequest: options
},
this.#requestOptions
);

return res.data.dynamicSecret;
return res.data.dynamicSecret;
} catch (err) {
throw newInfisicalError(err);
}
}

leases = {
create: async (options: DefaultApiApiV1DynamicSecretsLeasesPostRequest["apiV1DynamicSecretsLeasesPostRequest"]) => {
const res = await this.#apiInstance.apiV1DynamicSecretsLeasesPost(
{
apiV1DynamicSecretsLeasesPostRequest: options
},
this.#requestOptions
);
try {
const res = await this.#apiInstance.apiV1DynamicSecretsLeasesPost(
{
apiV1DynamicSecretsLeasesPostRequest: options
},
this.#requestOptions
);

return res.data;
return res.data;
} catch (err) {
throw newInfisicalError(err);
}
},
delete: async (
leaseId: string,
options: DefaultApiApiV1DynamicSecretsLeasesLeaseIdDeleteRequest["apiV1DynamicSecretsLeasesLeaseIdDeleteRequest"]
) => {
const res = await this.#apiInstance.apiV1DynamicSecretsLeasesLeaseIdDelete(
{
leaseId: leaseId,
apiV1DynamicSecretsLeasesLeaseIdDeleteRequest: options
},
this.#requestOptions
);
try {
const res = await this.#apiInstance.apiV1DynamicSecretsLeasesLeaseIdDelete(
{
leaseId: leaseId,
apiV1DynamicSecretsLeasesLeaseIdDeleteRequest: options
},
this.#requestOptions
);

return res.data;
return res.data;
} catch (err) {
throw newInfisicalError(err);
}
},

renew: async (
leaseId: string,
options: DefaultApiApiV1DynamicSecretsLeasesLeaseIdRenewPostRequest["apiV1DynamicSecretsLeasesLeaseIdRenewPostRequest"]
) => {
const res = await this.#apiInstance.apiV1DynamicSecretsLeasesLeaseIdRenewPost(
{
leaseId: leaseId,
apiV1DynamicSecretsLeasesLeaseIdRenewPostRequest: options
},
this.#requestOptions
);
try {
const res = await this.#apiInstance.apiV1DynamicSecretsLeasesLeaseIdRenewPost(
{
leaseId: leaseId,
apiV1DynamicSecretsLeasesLeaseIdRenewPostRequest: options
},
this.#requestOptions
);

return res.data;
return res.data;
} catch (err) {
throw newInfisicalError(err);
}
}
};
}
Loading