Skip to content

Commit

Permalink
Merge pull request #5 from Infisical/daniel/less-verbose-errors
Browse files Browse the repository at this point in the history
feat: less verbose errors
  • Loading branch information
DanielHougaard authored Oct 9, 2024
2 parents 0b1c62f + d41f4a3 commit d57a84c
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 125 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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 +57,11 @@ 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 +79,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 +90,13 @@ 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
86 changes: 65 additions & 21 deletions src/custom/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { InfisicalSDK } from "..";
import { ApiV1AuthUniversalAuthLoginPostRequest } from "../infisicalapi_client";
import { DefaultApi as InfisicalApi } from "../infisicalapi_client";
import { MACHINE_IDENTITY_ID_ENV_NAME } from "./constants";
import { InfisicalSDKError, newInfisicalError } from "./errors";
import { getAwsRegion, performAwsIamLogin } from "./util";

type AuthenticatorFunction = (accessToken: string) => InfisicalSDK;
Expand All @@ -10,47 +11,90 @@ type AwsAuthLoginOptions = {
identityId?: string;
};

export const renewToken = async (apiClient: InfisicalApi, token?: string) => {
try {
if (!token) {
throw new InfisicalSDKError("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;
#apiClient: InfisicalApi;
#baseUrl: string;
#accessToken?: string;

constructor(authenticator: AuthenticatorFunction, apiInstance: InfisicalApi, baseUrl: string) {
constructor(authenticator: AuthenticatorFunction, apiInstance: InfisicalApi, accessToken?: string) {
this.#sdkAuthenticator = 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 InfisicalSDKError("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
}
});

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

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

return this.#sdkAuthenticator(res.data.accessToken);
return this.#sdkAuthenticator(res.data.accessToken);
} catch (err) {
throw newInfisicalError(err);
}
},

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

Expand Down
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);
}
}
};
}
52 changes: 52 additions & 0 deletions src/custom/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { AxiosError } from "axios";

type TApiErrorResponse = {
statusCode: number;
message: string;
error: string;
};
export class InfisicalSDKError extends Error {
constructor(message: string) {
super(message);
this.message = message;
this.name = "InfisicalSDKError";
}
}

export class InfisicalSDKRequestError extends Error {
constructor(
message: string,
requestData: {
url: string;
method: string;
statusCode: number;
}
) {
super(message);
this.message = `[URL=${requestData.url}] [Method=${requestData.method}] [StatusCode=${requestData.statusCode}] ${message}`;
this.name = "InfisicalSDKRequestError";
}
}

export const newInfisicalError = (error: any) => {
if (error instanceof AxiosError) {
const data = error?.response?.data as TApiErrorResponse;

if (data?.message) {
return new InfisicalSDKRequestError(data.message, {
url: error.response?.config.url || "",
method: error.response?.config.method || "",
statusCode: error.response?.status || 0
});
} else if (error.message) {
return new InfisicalSDKError(error.message);
} else if (error.code) {
// If theres no message but a code is present, it's likely to be an aggregation error. This is not specific to Axios, but it falls under the AxiosError type
return new InfisicalSDKError(error.code);
} else {
return new InfisicalSDKError("Request failed with unknown error");
}
}

return new InfisicalSDKError(error?.message || "An error occurred");
};
17 changes: 15 additions & 2 deletions src/custom/schemas/dynamic-secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,17 @@ export const AzureEntraIDSchema = z.object({
clientSecret: z.string().trim().min(1)
});

export const LdapSchema = z.object({
url: z.string().trim().min(1),
binddn: z.string().trim().min(1),
bindpass: z.string().trim().min(1),
ca: z.string().optional(),

creationLdif: z.string().min(1),
revocationLdif: z.string().min(1),
rollbackLdif: z.string().optional()
});

export enum DynamicSecretProviders {
SqlDatabase = "sql-database",
Cassandra = "cassandra",
Expand All @@ -175,7 +186,8 @@ export enum DynamicSecretProviders {
ElasticSearch = "elastic-search",
MongoDB = "mongo-db",
RabbitMq = "rabbit-mq",
AzureEntraID = "azure-entra-id"
AzureEntraID = "azure-entra-id",
Ldap = "ldap"
}

export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
Expand All @@ -188,7 +200,8 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(DynamicSecretProviders.ElasticSearch), inputs: DynamicSecretElasticSearchSchema }),
z.object({ type: z.literal(DynamicSecretProviders.MongoDB), inputs: DynamicSecretMongoDBSchema }),
z.object({ type: z.literal(DynamicSecretProviders.RabbitMq), inputs: DynamicSecretRabbitMqSchema }),
z.object({ type: z.literal(DynamicSecretProviders.AzureEntraID), inputs: AzureEntraIDSchema })
z.object({ type: z.literal(DynamicSecretProviders.AzureEntraID), inputs: AzureEntraIDSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Ldap), inputs: LdapSchema })
]);

export type TDynamicSecretProvider = z.infer<typeof DynamicSecretProviderSchema>;
Loading

0 comments on commit d57a84c

Please sign in to comment.