mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 20:27:43 +00:00
improvements: address feedback
This commit is contained in:
+7
-2
@@ -10,6 +10,7 @@ import {
|
|||||||
TRotationFactoryRotateCredentials
|
TRotationFactoryRotateCredentials
|
||||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
import { getAuth0ConnectionAccessToken } from "@app/services/app-connection/auth0";
|
import { getAuth0ConnectionAccessToken } from "@app/services/app-connection/auth0";
|
||||||
|
|
||||||
import { generatePassword } from "../shared/utils";
|
import { generatePassword } from "../shared/utils";
|
||||||
@@ -26,11 +27,13 @@ export const auth0ClientSecretRotationFactory: TRotationFactory<
|
|||||||
|
|
||||||
const $rotateClientSecret = async () => {
|
const $rotateClientSecret = async () => {
|
||||||
const accessToken = await getAuth0ConnectionAccessToken(connection, appConnectionDAL, kmsService);
|
const accessToken = await getAuth0ConnectionAccessToken(connection, appConnectionDAL, kmsService);
|
||||||
|
const { audience } = connection.credentials;
|
||||||
|
await blockLocalAndPrivateIpAddresses(audience);
|
||||||
const clientSecret = generatePassword();
|
const clientSecret = generatePassword();
|
||||||
|
|
||||||
await request.request({
|
await request.request({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: `${connection.credentials.audience}clients/${clientId}`,
|
url: `${audience}clients/${clientId}`,
|
||||||
headers: { authorization: `Bearer ${accessToken}` },
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
data: {
|
data: {
|
||||||
client_secret: clientSecret
|
client_secret: clientSecret
|
||||||
@@ -53,11 +56,13 @@ export const auth0ClientSecretRotationFactory: TRotationFactory<
|
|||||||
callback
|
callback
|
||||||
) => {
|
) => {
|
||||||
const accessToken = await getAuth0ConnectionAccessToken(connection, appConnectionDAL, kmsService);
|
const accessToken = await getAuth0ConnectionAccessToken(connection, appConnectionDAL, kmsService);
|
||||||
|
const { audience } = connection.credentials;
|
||||||
|
await blockLocalAndPrivateIpAddresses(audience);
|
||||||
|
|
||||||
// we just trigger an auth0 rotation to negate our credentials
|
// we just trigger an auth0 rotation to negate our credentials
|
||||||
await request.request({
|
await request.request({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${connection.credentials.audience}clients/${clientId}/rotate-secret`,
|
url: `${audience}clients/${clientId}/rotate-secret`,
|
||||||
headers: { authorization: `Bearer ${accessToken}` }
|
headers: { authorization: `Bearer ${accessToken}` }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ const Auth0ClientSecretRotationParametersSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const Auth0ClientSecretRotationSecretsMappingSchema = z.object({
|
const Auth0ClientSecretRotationSecretsMappingSchema = z.object({
|
||||||
clientId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AUTH0_CLIENT_SECRET.clientID),
|
clientId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AUTH0_CLIENT_SECRET.clientId),
|
||||||
clientSecret: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AUTH0_CLIENT_SECRET.clientSecret)
|
clientSecret: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AUTH0_CLIENT_SECRET.clientSecret)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1979,7 +1979,7 @@ export const SecretRotations = {
|
|||||||
password: "The name of the secret that the generated password will be mapped to."
|
password: "The name of the secret that the generated password will be mapped to."
|
||||||
},
|
},
|
||||||
AUTH0_CLIENT_SECRET: {
|
AUTH0_CLIENT_SECRET: {
|
||||||
clientID: "The name of the secret that the client ID will be mapped to.",
|
clientId: "The name of the secret that the client ID will be mapped to.",
|
||||||
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import {
|
import {
|
||||||
CreateAuth0ConnectionSchema,
|
CreateAuth0ConnectionSchema,
|
||||||
SanitizedAuth0ConnectionSchema,
|
SanitizedAuth0ConnectionSchema,
|
||||||
UpdateAuth0ConnectionSchema
|
UpdateAuth0ConnectionSchema
|
||||||
} from "@app/services/app-connection/auth0";
|
} from "@app/services/app-connection/auth0";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
@@ -15,4 +20,32 @@ export const registerAuth0ConnectionRouter = async (server: FastifyZodProvider)
|
|||||||
createSchema: CreateAuth0ConnectionSchema,
|
createSchema: CreateAuth0ConnectionSchema,
|
||||||
updateSchema: UpdateAuth0ConnectionSchema
|
updateSchema: UpdateAuth0ConnectionSchema
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The below endpoints are not exposed and for Infisical App use
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/clients`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
clients: z.object({ name: z.string(), id: z.string() }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const clients = await server.services.appConnection.auth0.listClients(connectionId, req.permission);
|
||||||
|
|
||||||
|
return { clients };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM,
|
TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM,
|
||||||
validateAppConnectionCredentials
|
validateAppConnectionCredentials
|
||||||
} from "@app/services/app-connection/app-connection-fns";
|
} from "@app/services/app-connection/app-connection-fns";
|
||||||
|
import { auth0ConnectionService } from "@app/services/app-connection/auth0/auth0-connection-service";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
import { TAppConnectionDALFactory } from "./app-connection-dal";
|
import { TAppConnectionDALFactory } from "./app-connection-dal";
|
||||||
@@ -440,6 +441,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
aws: awsConnectionService(connectAppConnectionById),
|
aws: awsConnectionService(connectAppConnectionById),
|
||||||
humanitec: humanitecConnectionService(connectAppConnectionById),
|
humanitec: humanitecConnectionService(connectAppConnectionById),
|
||||||
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
vercel: vercelConnectionService(connectAppConnectionById)
|
vercel: vercelConnectionService(connectAppConnectionById),
|
||||||
|
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { AxiosError } from "axios";
|
|
||||||
|
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
@@ -45,7 +43,11 @@ const authorizeAuth0Connection = async ({
|
|||||||
throw new Error(`Unhandled token type: ${data.token_type}`);
|
throw new Error(`Unhandled token type: ${data.token_type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { accessToken: data.access_token, expiresAt: data.expires_in * 1000 + Date.now() };
|
return {
|
||||||
|
accessToken: data.access_token,
|
||||||
|
// cap token lifespan to 10 minutes
|
||||||
|
expiresAt: Math.min(data.expires_in * 1000, 600000) + Date.now()
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAuth0ConnectionAccessToken = async (
|
export const getAuth0ConnectionAccessToken = async (
|
||||||
@@ -53,7 +55,12 @@ export const getAuth0ConnectionAccessToken = async (
|
|||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
) => {
|
) => {
|
||||||
// just getting a new auth token every time because if permissions change the previous token doesn't have the changes
|
const { expiresAt, accessToken } = credentials;
|
||||||
|
|
||||||
|
// get new token if expired or less than 5 minutes until expiry
|
||||||
|
if (Date.now() < expiresAt - 300000) {
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
const authData = await authorizeAuth0Connection(credentials);
|
const authData = await authorizeAuth0Connection(credentials);
|
||||||
|
|
||||||
@@ -84,7 +91,7 @@ export const validateAuth0ConnectionCredentials = async ({ credentials }: TAuth0
|
|||||||
};
|
};
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: (e as AxiosError).message ?? `Unable to validate connection: verify credentials`
|
message: (e as Error).message ?? `Unable to validate connection: verify credentials`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { getAuth0ConnectionAccessToken } from "@app/services/app-connection/auth0/auth0-connection-fns";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
|
import { TAuth0Connection, TAuth0ListClient, TAuth0ListClientsResponse } from "./auth0-connection-types";
|
||||||
|
|
||||||
|
type TGetAppConnectionFunc = (
|
||||||
|
app: AppConnection,
|
||||||
|
connectionId: string,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => Promise<TAuth0Connection>;
|
||||||
|
|
||||||
|
const listAuth0Clients = async (
|
||||||
|
appConnection: TAuth0Connection,
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
|
) => {
|
||||||
|
const accessToken = await getAuth0ConnectionAccessToken(appConnection, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
const { audience, clientId: connectionClientId } = appConnection.credentials;
|
||||||
|
await blockLocalAndPrivateIpAddresses(audience);
|
||||||
|
|
||||||
|
const clients: TAuth0ListClient[] = [];
|
||||||
|
let hasMore = true;
|
||||||
|
let page = 0;
|
||||||
|
|
||||||
|
while (hasMore) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data: clientsPage } = await request.get<TAuth0ListClientsResponse>(`${audience}clients`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
include_totals: true,
|
||||||
|
per_page: 100,
|
||||||
|
page
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
clients.push(...clientsPage.clients);
|
||||||
|
page += 1;
|
||||||
|
hasMore = clientsPage.total > clients.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
clients.filter((client) => client.client_id !== connectionClientId && client.name !== "All Applications") ?? []
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const auth0ConnectionService = (
|
||||||
|
getAppConnection: TGetAppConnectionFunc,
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
|
) => {
|
||||||
|
const listClients = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.Auth0, connectionId, actor);
|
||||||
|
|
||||||
|
const clients = await listAuth0Clients(appConnection, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
return clients.map((client) => ({ id: client.client_id, name: client.name }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
listClients
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -27,3 +27,13 @@ export type TAuth0AccessTokenResponse = {
|
|||||||
scope: string;
|
scope: string;
|
||||||
token_type: string;
|
token_type: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TAuth0ListClient = {
|
||||||
|
name: string;
|
||||||
|
client_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAuth0ListClientsResponse = {
|
||||||
|
total: number;
|
||||||
|
clients: TAuth0ListClient[];
|
||||||
|
};
|
||||||
|
|||||||
@@ -13,11 +13,7 @@ description: "Learn how to automatically rotate Auth0 Client Secrets."
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
1. Create an [Auth0 Connection](/integrations/app-connections/auth0) with the required **Secret Rotation** audience and permissions
|
- Create an [Auth0 Connection](/integrations/app-connections/auth0) with the required **Secret Rotation** audience and permissions
|
||||||
2. Copy the **Client ID** of the application in Auth0 you want to rotate the Client Secret for:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## Create an Auth0 Client Secret Rotation in Infisical
|
## Create an Auth0 Client Secret Rotation in Infisical
|
||||||
|
|
||||||
@@ -36,12 +32,14 @@ description: "Learn how to automatically rotate Auth0 Client Secrets."
|
|||||||
- **Rotation Interval** - the interval, in days, that once elapsed will trigger a rotation.
|
- **Rotation Interval** - the interval, in days, that once elapsed will trigger a rotation.
|
||||||
- **Rotate At** - the local time of day when rotation should occur once the interval has elapsed.
|
- **Rotate At** - the local time of day when rotation should occur once the interval has elapsed.
|
||||||
- **Auto-Rotation Enabled** - whether secrets should automatically be rotated once the rotation interval has elapsed. Disable this option to manually rotate secrets or pause secret rotation.
|
- **Auto-Rotation Enabled** - whether secrets should automatically be rotated once the rotation interval has elapsed. Disable this option to manually rotate secrets or pause secret rotation.
|
||||||
|
<Note>
|
||||||
|
Due to Auth0 Client Secret Rotations rotating a single credential set, auto-rotation may result in service interruptions. If you need to ensure service continuity, we recommend disabling this option.
|
||||||
|
</Note>
|
||||||
|
|
||||||
4. Input the Client ID of the Auth0 application acquired above that will have its Client Secret rotated. Then click **Next**.
|
|
||||||
|
4. Select the Auth0 application whose Client Secret you want to rotate. Then click **Next**.
|
||||||

|

|
||||||
|
|
||||||
- **Client ID** - the Client ID of the application whose Client Secret will be rotated.
|
|
||||||
|
|
||||||
5. Specify the secret names that the client credentials should be mapped to. Then click **Next**.
|
5. Specify the secret names that the client credentials should be mapped to. Then click **Next**.
|
||||||

|

|
||||||
|
|
||||||
@@ -64,6 +62,10 @@ description: "Learn how to automatically rotate Auth0 Client Secrets."
|
|||||||
To create an Auth0 Client Secret Rotation, make an API request to the [Create Auth0
|
To create an Auth0 Client Secret Rotation, make an API request to the [Create Auth0
|
||||||
Client Secret Rotation](/api-reference/endpoints/secret-rotations/auth0-client-secret/create) API endpoint.
|
Client Secret Rotation](/api-reference/endpoints/secret-rotations/auth0-client-secret/create) API endpoint.
|
||||||
|
|
||||||
|
You will first need the **Client ID** of the Auth0 application you want to rotate the secret for. This can be obtained from the Applications dashboard.
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
### Sample request
|
### Sample request
|
||||||
|
|
||||||
```bash Request
|
```bash Request
|
||||||
@@ -71,9 +73,9 @@ description: "Learn how to automatically rotate Auth0 Client Secrets."
|
|||||||
--url https://us.infisical.com/api/v2/secret-rotations/auth0-client-secret \
|
--url https://us.infisical.com/api/v2/secret-rotations/auth0-client-secret \
|
||||||
--header 'Content-Type: application/json' \
|
--header 'Content-Type: application/json' \
|
||||||
--data '{
|
--data '{
|
||||||
"name": "my-pg-rotation",
|
"name": "my-auth0-rotation",
|
||||||
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
"description": "my database credentials rotation",
|
"description": "my client secret rotation",
|
||||||
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
"environment": "dev",
|
"environment": "dev",
|
||||||
"secretPath": "/",
|
"secretPath": "/",
|
||||||
@@ -93,14 +95,18 @@ description: "Learn how to automatically rotate Auth0 Client Secrets."
|
|||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Due to Auth0 Client Secret Rotations rotating a single credential set, auto-rotation may result in service interruptions. If you need to ensure service continuity, we recommend disabling this option.
|
||||||
|
</Note>
|
||||||
|
|
||||||
### Sample response
|
### Sample response
|
||||||
|
|
||||||
```bash Response
|
```bash Response
|
||||||
{
|
{
|
||||||
"secretRotation": {
|
"secretRotation": {
|
||||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
"name": "my-pg-rotation",
|
"name": "my-auth0-rotation",
|
||||||
"description": "my database credentials rotation",
|
"description": "my client secret rotation",
|
||||||
"secretsMapping": {
|
"secretsMapping": {
|
||||||
"clientId": "AUTH0_CLIENT_ID",
|
"clientId": "AUTH0_CLIENT_ID",
|
||||||
"clientSecret": "AUTH0_CLIENT_SECRET"
|
"clientSecret": "AUTH0_CLIENT_SECRET"
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 394 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 712 KiB After Width: | Height: | Size: 709 KiB |
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Auth0 Connection"
|
title: "Auth0 Connection"
|
||||||
description: "Learn how to configure a Auth0 Connection for Infisical."
|
description: "Learn how to configure an Auth0 Connection for Infisical."
|
||||||
---
|
---
|
||||||
|
|
||||||
Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow) to connect with your Auth0 applications.
|
Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow) to connect with your Auth0 applications.
|
||||||
@@ -22,7 +22,7 @@ Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-st
|
|||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="Secret Rotation">
|
<Tab title="Secret Rotation">
|
||||||
Select the **Auth0 Management API** option from the dropdown and grant the `update:client_keys` permission.
|
Select the **Auth0 Management API** option from the dropdown and grant the `update:client_keys` and `read:clients` permission.
|
||||||

|

|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -72,7 +72,7 @@ Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-st
|
|||||||
"clientId": "...",
|
"clientId": "...",
|
||||||
"clientSecret": "...",
|
"clientSecret": "...",
|
||||||
"audience": "https://xxx-xxxxxxxxx.us.auth0.com/api/v2/"
|
"audience": "https://xxx-xxxxxxxxx.us.auth0.com/api/v2/"
|
||||||
},
|
}
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+18
-2
@@ -1,8 +1,14 @@
|
|||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { format, setHours, setMinutes } from "date-fns";
|
import { format, setHours, setMinutes } from "date-fns";
|
||||||
|
|
||||||
import { FilterableSelect, FormControl, Input, Switch } from "@app/components/v2";
|
import { FilterableSelect, FormControl, Input, Switch } from "@app/components/v2";
|
||||||
import { getRotateAtLocal } from "@app/helpers/secretRotationsV2";
|
import {
|
||||||
|
getRotateAtLocal,
|
||||||
|
IS_ROTATION_DUAL_CREDENTIALS,
|
||||||
|
SECRET_ROTATION_MAP
|
||||||
|
} from "@app/helpers/secretRotationsV2";
|
||||||
import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
|
import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
import { TSecretRotationV2Form } from "./schemas";
|
import { TSecretRotationV2Form } from "./schemas";
|
||||||
@@ -14,7 +20,9 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }: Props) => {
|
export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }: Props) => {
|
||||||
const { control } = useFormContext<TSecretRotationV2Form>();
|
const { control, watch } = useFormContext<TSecretRotationV2Form>();
|
||||||
|
|
||||||
|
const [type, isAutoRotationEnabled] = watch(["type", "isAutoRotationEnabled"]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -117,6 +125,14 @@ export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }:
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{!IS_ROTATION_DUAL_CREDENTIALS[type] && isAutoRotationEnabled && (
|
||||||
|
<div className="rounded border border-yellow bg-yellow/10 p-2 px-3 text-sm text-yellow">
|
||||||
|
<FontAwesomeIcon icon={faWarning} className="mr-1" /> Due to{" "}
|
||||||
|
{SECRET_ROTATION_MAP[type].name} Rotations rotating a single credential set, auto-rotation
|
||||||
|
may result in service interruptions. If you need to ensure service continuity, we
|
||||||
|
recommend disabling this option.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { SecretRotationV2ReviewFields } from "@app/components/secret-rotations-v
|
|||||||
import { SecretRotationV2SecretsMappingFields } from "@app/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields";
|
import { SecretRotationV2SecretsMappingFields } from "@app/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields";
|
||||||
import { Button } from "@app/components/v2";
|
import { Button } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
|
import { IS_ROTATION_DUAL_CREDENTIALS, SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
|
||||||
import {
|
import {
|
||||||
SecretRotation,
|
SecretRotation,
|
||||||
TSecretRotationV2,
|
TSecretRotationV2,
|
||||||
@@ -84,7 +84,7 @@ export const SecretRotationV2Form = ({
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
type,
|
type,
|
||||||
isAutoRotationEnabled: true,
|
isAutoRotationEnabled: IS_ROTATION_DUAL_CREDENTIALS[type],
|
||||||
rotationInterval: DEFAULT_ROTATION_INTERVAL,
|
rotationInterval: DEFAULT_ROTATION_INTERVAL,
|
||||||
rotateAtUtc: {
|
rotateAtUtc: {
|
||||||
hours: 0,
|
hours: 0,
|
||||||
|
|||||||
+47
-7
@@ -1,30 +1,70 @@
|
|||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
import { SingleValue } from "react-select";
|
||||||
|
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
|
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
|
||||||
import { FormControl, Input } from "@app/components/v2";
|
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
|
||||||
|
import { useAuth0ConnectionListClients } from "@app/hooks/api/appConnections/auth0";
|
||||||
|
import { TAuth0Client } from "@app/hooks/api/appConnections/auth0/types";
|
||||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||||
|
|
||||||
export const Auth0ClientSecretRotationParametersFields = () => {
|
export const Auth0ClientSecretRotationParametersFields = () => {
|
||||||
const { control } = useFormContext<
|
const { control, watch } = useFormContext<
|
||||||
TSecretRotationV2Form & {
|
TSecretRotationV2Form & {
|
||||||
type: SecretRotation.Auth0ClientSecret;
|
type: SecretRotation.Auth0ClientSecret;
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
const connectionId = watch("connection.id");
|
||||||
|
|
||||||
|
const { data: clients, isPending: isClientsPending } = useAuth0ConnectionListClients(
|
||||||
|
connectionId,
|
||||||
|
{ enabled: Boolean(connectionId) }
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Controller
|
<Controller
|
||||||
|
name="parameters.clientId"
|
||||||
|
control={control}
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
tooltipText="The Client ID of the Auth0 Application you want to rotate the client secret for."
|
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
label="Client ID"
|
label="Application"
|
||||||
|
helperText={
|
||||||
|
<Tooltip
|
||||||
|
className="max-w-md"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
Ensure that your connection has the{" "}
|
||||||
|
<span className="font-semibold">read_clients</span> permission and the application
|
||||||
|
exists in the connection's audience.
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span>Don't see the application you're looking for?</span>{" "}
|
||||||
|
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Input value={value} onChange={onChange} />
|
<FilterableSelect
|
||||||
|
menuPlacement="top"
|
||||||
|
isLoading={isClientsPending && Boolean(connectionId)}
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
value={clients?.find((client) => client.id === value) ?? null}
|
||||||
|
onChange={(option) => {
|
||||||
|
onChange((option as SingleValue<TAuth0Client>)?.id ?? null);
|
||||||
|
}}
|
||||||
|
options={clients}
|
||||||
|
placeholder="Select an application..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.id}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
control={control}
|
|
||||||
name="parameters.clientId"
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ import { SecretRotation, useSecretRotationV2Option } from "@app/hooks/api/secret
|
|||||||
export const SqlCredentialsRotationParametersFields = () => {
|
export const SqlCredentialsRotationParametersFields = () => {
|
||||||
const { control, watch } = useFormContext<
|
const { control, watch } = useFormContext<
|
||||||
TSecretRotationV2Form & {
|
TSecretRotationV2Form & {
|
||||||
type: SecretRotation.PostgresCredentials; // all sql rotations share these fields
|
type: SecretRotation.PostgresCredentials | SecretRotation.MsSqlCredentials;
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ import { SecretRotationReviewSection } from "./SecretRotationReviewSection";
|
|||||||
export const SqlCredentialsRotationReviewFields = () => {
|
export const SqlCredentialsRotationReviewFields = () => {
|
||||||
const { watch } = useFormContext<
|
const { watch } = useFormContext<
|
||||||
TSecretRotationV2Form & {
|
TSecretRotationV2Form & {
|
||||||
type: SecretRotation.PostgresCredentials; // all sql rotations share these fields
|
type: SecretRotation.PostgresCredentials | SecretRotation.MsSqlCredentials;
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ import { SecretsMappingTable } from "./SecretsMappingTable";
|
|||||||
export const SqlCredentialsRotationSecretsMappingFields = () => {
|
export const SqlCredentialsRotationSecretsMappingFields = () => {
|
||||||
const { control, watch } = useFormContext<
|
const { control, watch } = useFormContext<
|
||||||
TSecretRotationV2Form & {
|
TSecretRotationV2Form & {
|
||||||
type: SecretRotation.PostgresCredentials; // all sql rotations share these fields
|
type: SecretRotation.PostgresCredentials | SecretRotation.MsSqlCredentials;
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnectio
|
|||||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0
|
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// if a rotation can potentially have downtime due to rotating a single credential set this to false
|
||||||
|
export const IS_ROTATION_DUAL_CREDENTIALS: Record<SecretRotation, boolean> = {
|
||||||
|
[SecretRotation.PostgresCredentials]: true,
|
||||||
|
[SecretRotation.MsSqlCredentials]: true,
|
||||||
|
[SecretRotation.Auth0ClientSecret]: false
|
||||||
|
};
|
||||||
|
|
||||||
export const getRotateAtLocal = ({ hours, minutes }: TSecretRotationV2["rotateAtUtc"]) => {
|
export const getRotateAtLocal = ({ hours, minutes }: TSecretRotationV2["rotateAtUtc"]) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./queries";
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { appConnectionKeys } from "../queries";
|
||||||
|
import { TAuth0Client } from "./types";
|
||||||
|
|
||||||
|
const auth0ConnectionKeys = {
|
||||||
|
all: [...appConnectionKeys.all, "auth0"] as const,
|
||||||
|
listClients: (connectionId: string) =>
|
||||||
|
[...auth0ConnectionKeys.all, "clients", connectionId] as const
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAuth0ConnectionListClients = (
|
||||||
|
connectionId: string,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
TAuth0Client[],
|
||||||
|
unknown,
|
||||||
|
TAuth0Client[],
|
||||||
|
ReturnType<typeof auth0ConnectionKeys.listClients>
|
||||||
|
>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: auth0ConnectionKeys.listClients(connectionId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<{ clients: TAuth0Client[] }>(
|
||||||
|
`/api/v1/app-connections/auth0/${connectionId}/clients`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.clients;
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type TAuth0Client = {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
+1
-1
@@ -70,7 +70,7 @@ export const Auth0ConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
|||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
tooltipText={`The method you would like to use to connect with ${
|
tooltipText={`The method you would like to use to connect with ${
|
||||||
APP_CONNECTION_MAP[AppConnection.Databricks].name
|
APP_CONNECTION_MAP[AppConnection.Auth0].name
|
||||||
}. This field cannot be changed after creation.`}
|
}. This field cannot be changed after creation.`}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
|
|||||||
Reference in New Issue
Block a user