mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 01:29:26 +00:00
merge from main
This commit is contained in:
@@ -188,7 +188,13 @@ export enum TableName {
|
|||||||
Relay = "relays",
|
Relay = "relays",
|
||||||
GatewayV2 = "gateways_v2",
|
GatewayV2 = "gateways_v2",
|
||||||
|
|
||||||
KeyValueStore = "key_value_store"
|
KeyValueStore = "key_value_store",
|
||||||
|
|
||||||
|
// PAM
|
||||||
|
PamFolder = "pam_folders",
|
||||||
|
PamResource = "pam_resources",
|
||||||
|
PamAccount = "pam_accounts",
|
||||||
|
PamSession = "pam_sessions"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
|
||||||
|
|||||||
@@ -84,69 +84,70 @@ export const identityUaServiceFactory = ({
|
|||||||
|
|
||||||
const LOCKOUT_KEY = `lockout:identity:${identityUa.identityId}:${IdentityAuthMethod.UNIVERSAL_AUTH}:${clientId}`;
|
const LOCKOUT_KEY = `lockout:identity:${identityUa.identityId}:${IdentityAuthMethod.UNIVERSAL_AUTH}:${clientId}`;
|
||||||
|
|
||||||
let lock: Awaited<ReturnType<typeof keyStore.acquireLock>> | undefined;
|
const lockoutRaw = await keyStore.getItem(LOCKOUT_KEY);
|
||||||
if (identityUa.lockoutEnabled) {
|
|
||||||
try {
|
let lockout: LockoutObject | undefined;
|
||||||
lock = await keyStore.acquireLock([KeyStorePrefixes.IdentityLockoutLock(LOCKOUT_KEY)], 500, {
|
if (lockoutRaw) {
|
||||||
retryCount: 3,
|
lockout = JSON.parse(lockoutRaw) as LockoutObject;
|
||||||
retryDelay: 300,
|
}
|
||||||
retryJitter: 100
|
|
||||||
});
|
if (lockout && lockout.lockedOut) {
|
||||||
} catch (e) {
|
throw new UnauthorizedError({
|
||||||
logger.info(
|
message: "This identity auth method is temporarily locked, please try again later"
|
||||||
`identity login failed to acquire lock [identityId=${identityUa.identityId}] [authMethod=${IdentityAuthMethod.UNIVERSAL_AUTH}]`
|
});
|
||||||
);
|
}
|
||||||
throw new RateLimitError({ message: "Failed to acquire lock: rate limit exceeded" });
|
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityUa.identityId });
|
||||||
|
if (!identityMembershipOrg) {
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "Invalid credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientSecretPrefix = clientSecret.slice(0, 4);
|
||||||
|
const clientSecretInfo = await identityUaClientSecretDAL.find({
|
||||||
|
identityUAId: identityUa.id,
|
||||||
|
isClientSecretRevoked: false,
|
||||||
|
clientSecretPrefix
|
||||||
|
});
|
||||||
|
|
||||||
|
let validClientSecretInfo: (typeof clientSecretInfo)[0] | null = null;
|
||||||
|
for await (const info of clientSecretInfo) {
|
||||||
|
const isMatch = await crypto.hashing().compareHash(clientSecret, info.clientSecretHash);
|
||||||
|
|
||||||
|
if (isMatch) {
|
||||||
|
validClientSecretInfo = info;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (!validClientSecretInfo) {
|
||||||
const lockoutRaw = await keyStore.getItem(LOCKOUT_KEY);
|
if (identityUa.lockoutEnabled) {
|
||||||
|
let lock: Awaited<ReturnType<typeof keyStore.acquireLock>> | undefined;
|
||||||
|
try {
|
||||||
|
lock = await keyStore.acquireLock([KeyStorePrefixes.IdentityLockoutLock(LOCKOUT_KEY)], 300, {
|
||||||
|
retryCount: 3,
|
||||||
|
retryDelay: 300,
|
||||||
|
retryJitter: 100
|
||||||
|
});
|
||||||
|
|
||||||
let lockout: LockoutObject | undefined;
|
// Re-fetch the latest lockout data while holding the lock
|
||||||
if (lockoutRaw) {
|
const lockoutRawNew = await keyStore.getItem(LOCKOUT_KEY);
|
||||||
lockout = JSON.parse(lockoutRaw) as LockoutObject;
|
if (lockoutRawNew) {
|
||||||
}
|
lockout = JSON.parse(lockoutRawNew) as LockoutObject;
|
||||||
|
} else {
|
||||||
if (lockout && lockout.lockedOut) {
|
|
||||||
throw new UnauthorizedError({
|
|
||||||
message: "This identity auth method is temporarily locked, please try again later"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityUa.identityId });
|
|
||||||
if (!identityMembershipOrg) {
|
|
||||||
throw new UnauthorizedError({
|
|
||||||
message: "Invalid credentials"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientSecretPrefix = clientSecret.slice(0, 4);
|
|
||||||
const clientSecretInfo = await identityUaClientSecretDAL.find({
|
|
||||||
identityUAId: identityUa.id,
|
|
||||||
isClientSecretRevoked: false,
|
|
||||||
clientSecretPrefix
|
|
||||||
});
|
|
||||||
|
|
||||||
let validClientSecretInfo: (typeof clientSecretInfo)[0] | null = null;
|
|
||||||
for await (const info of clientSecretInfo) {
|
|
||||||
const isMatch = await crypto.hashing().compareHash(clientSecret, info.clientSecretHash);
|
|
||||||
|
|
||||||
if (isMatch) {
|
|
||||||
validClientSecretInfo = info;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validClientSecretInfo) {
|
|
||||||
if (identityUa.lockoutEnabled) {
|
|
||||||
if (!lockout) {
|
|
||||||
lockout = {
|
lockout = {
|
||||||
lockedOut: false,
|
lockedOut: false,
|
||||||
failedAttempts: 0
|
failedAttempts: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lockout.lockedOut) {
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "This identity auth method is temporarily locked, please try again later"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
lockout.failedAttempts += 1;
|
lockout.failedAttempts += 1;
|
||||||
if (lockout.failedAttempts >= identityUa.lockoutThreshold) {
|
if (lockout.failedAttempts >= identityUa.lockoutThreshold) {
|
||||||
lockout.lockedOut = true;
|
lockout.lockedOut = true;
|
||||||
@@ -157,110 +158,121 @@ export const identityUaServiceFactory = ({
|
|||||||
lockout.lockedOut ? identityUa.lockoutDurationSeconds : identityUa.lockoutCounterResetSeconds,
|
lockout.lockedOut ? identityUa.lockoutDurationSeconds : identityUa.lockoutCounterResetSeconds,
|
||||||
JSON.stringify(lockout)
|
JSON.stringify(lockout)
|
||||||
);
|
);
|
||||||
}
|
} catch (e) {
|
||||||
|
if (lock === undefined) {
|
||||||
throw new UnauthorizedError({ message: "Invalid credentials" });
|
logger.info(
|
||||||
} else if (lockout) {
|
`identity login failed to acquire lock [identityId=${identityUa.identityId}] [authMethod=${IdentityAuthMethod.UNIVERSAL_AUTH}]`
|
||||||
await keyStore.deleteItem(LOCKOUT_KEY);
|
);
|
||||||
}
|
throw new RateLimitError({ message: "Failed to acquire lock: rate limit exceeded" });
|
||||||
|
}
|
||||||
const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo;
|
throw e;
|
||||||
if (Number(clientSecretTTL) > 0) {
|
} finally {
|
||||||
const clientSecretCreated = new Date(validClientSecretInfo.createdAt);
|
if (lock) {
|
||||||
const ttlInMilliseconds = Number(clientSecretTTL) * 1000;
|
await lock.release();
|
||||||
const currentDate = new Date();
|
}
|
||||||
const expirationTime = new Date(clientSecretCreated.getTime() + ttlInMilliseconds);
|
|
||||||
|
|
||||||
if (currentDate > expirationTime) {
|
|
||||||
await identityUaClientSecretDAL.updateById(validClientSecretInfo.id, {
|
|
||||||
isClientSecretRevoked: true
|
|
||||||
});
|
|
||||||
|
|
||||||
throw new UnauthorizedError({
|
|
||||||
message: "Access denied due to expired client secret"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientSecretNumUsesLimit > 0 && clientSecretNumUses >= clientSecretNumUsesLimit) {
|
throw new UnauthorizedError({ message: "Invalid credentials" });
|
||||||
// number of times client secret can be used for
|
} else if (lockout) {
|
||||||
// a login operation reached
|
// If credentials are valid, clear any existing lockout record
|
||||||
|
await keyStore.deleteItem(LOCKOUT_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo;
|
||||||
|
if (Number(clientSecretTTL) > 0) {
|
||||||
|
const clientSecretCreated = new Date(validClientSecretInfo.createdAt);
|
||||||
|
const ttlInMilliseconds = Number(clientSecretTTL) * 1000;
|
||||||
|
const currentDate = new Date();
|
||||||
|
const expirationTime = new Date(clientSecretCreated.getTime() + ttlInMilliseconds);
|
||||||
|
|
||||||
|
if (currentDate > expirationTime) {
|
||||||
await identityUaClientSecretDAL.updateById(validClientSecretInfo.id, {
|
await identityUaClientSecretDAL.updateById(validClientSecretInfo.id, {
|
||||||
isClientSecretRevoked: true
|
isClientSecretRevoked: true
|
||||||
});
|
});
|
||||||
|
|
||||||
throw new UnauthorizedError({
|
throw new UnauthorizedError({
|
||||||
message: "Access denied due to client secret usage limit reached"
|
message: "Access denied due to expired client secret"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const accessTokenTTLParams =
|
if (clientSecretNumUsesLimit > 0 && clientSecretNumUses >= clientSecretNumUsesLimit) {
|
||||||
Number(identityUa.accessTokenPeriod) === 0
|
// number of times client secret can be used for
|
||||||
? {
|
// a login operation reached
|
||||||
accessTokenTTL: identityUa.accessTokenTTL,
|
await identityUaClientSecretDAL.updateById(validClientSecretInfo.id, {
|
||||||
accessTokenMaxTTL: identityUa.accessTokenMaxTTL
|
isClientSecretRevoked: true
|
||||||
}
|
|
||||||
: {
|
|
||||||
accessTokenTTL: identityUa.accessTokenPeriod,
|
|
||||||
// We set a very large Max TTL for periodic tokens to ensure that clients (even outdated ones) can always renew their token
|
|
||||||
// without them having to update their SDKs, CLIs, etc. This workaround sets it to 30 years to emulate "forever"
|
|
||||||
accessTokenMaxTTL: 1000000000
|
|
||||||
};
|
|
||||||
|
|
||||||
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
|
|
||||||
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx);
|
|
||||||
await identityOrgMembershipDAL.updateById(
|
|
||||||
identityMembershipOrg.id,
|
|
||||||
{
|
|
||||||
lastLoginAuthMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
|
|
||||||
lastLoginTime: new Date()
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
const newToken = await identityAccessTokenDAL.create(
|
|
||||||
{
|
|
||||||
identityId: identityUa.identityId,
|
|
||||||
isAccessTokenRevoked: false,
|
|
||||||
identityUAClientSecretId: uaClientSecretDoc.id,
|
|
||||||
accessTokenNumUses: 0,
|
|
||||||
accessTokenNumUsesLimit: identityUa.accessTokenNumUsesLimit,
|
|
||||||
accessTokenPeriod: identityUa.accessTokenPeriod,
|
|
||||||
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
|
|
||||||
...accessTokenTTLParams
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
return newToken;
|
|
||||||
});
|
});
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "Access denied due to client secret usage limit reached"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const accessTokenTTLParams =
|
||||||
const accessToken = crypto.jwt().sign(
|
Number(identityUa.accessTokenPeriod) === 0
|
||||||
|
? {
|
||||||
|
accessTokenTTL: identityUa.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityUa.accessTokenMaxTTL
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
accessTokenTTL: identityUa.accessTokenPeriod,
|
||||||
|
// We set a very large Max TTL for periodic tokens to ensure that clients (even outdated ones) can always renew their token
|
||||||
|
// without them having to update their SDKs, CLIs, etc. This workaround sets it to 30 years to emulate "forever"
|
||||||
|
accessTokenMaxTTL: 1000000000
|
||||||
|
};
|
||||||
|
|
||||||
|
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
|
||||||
|
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx);
|
||||||
|
await identityOrgMembershipDAL.updateById(
|
||||||
|
identityMembershipOrg.id,
|
||||||
|
{
|
||||||
|
lastLoginAuthMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
|
||||||
|
lastLoginTime: new Date()
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
const newToken = await identityAccessTokenDAL.create(
|
||||||
{
|
{
|
||||||
identityId: identityUa.identityId,
|
identityId: identityUa.identityId,
|
||||||
clientSecretId: validClientSecretInfo.id,
|
isAccessTokenRevoked: false,
|
||||||
identityAccessTokenId: identityAccessToken.id,
|
identityUAClientSecretId: uaClientSecretDoc.id,
|
||||||
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
|
accessTokenNumUses: 0,
|
||||||
} as TIdentityAccessTokenJwtPayload,
|
accessTokenNumUsesLimit: identityUa.accessTokenNumUsesLimit,
|
||||||
appCfg.AUTH_SECRET,
|
accessTokenPeriod: identityUa.accessTokenPeriod,
|
||||||
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
|
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
|
||||||
Number(identityAccessToken.accessTokenTTL) === 0
|
...accessTokenTTLParams
|
||||||
? undefined
|
},
|
||||||
: {
|
tx
|
||||||
expiresIn: Number(identityAccessToken.accessTokenTTL)
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return newToken;
|
||||||
accessToken,
|
});
|
||||||
identityUa,
|
|
||||||
validClientSecretInfo,
|
const appCfg = getConfig();
|
||||||
identityAccessToken,
|
const accessToken = crypto.jwt().sign(
|
||||||
identityMembershipOrg,
|
{
|
||||||
...accessTokenTTLParams
|
identityId: identityUa.identityId,
|
||||||
};
|
clientSecretId: validClientSecretInfo.id,
|
||||||
} finally {
|
identityAccessTokenId: identityAccessToken.id,
|
||||||
if (lock) await lock.release();
|
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
|
||||||
}
|
} as TIdentityAccessTokenJwtPayload,
|
||||||
|
appCfg.AUTH_SECRET,
|
||||||
|
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
|
||||||
|
Number(identityAccessToken.accessTokenTTL) === 0
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
expiresIn: Number(identityAccessToken.accessTokenTTL)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken,
|
||||||
|
identityUa,
|
||||||
|
validClientSecretInfo,
|
||||||
|
identityAccessToken,
|
||||||
|
identityMembershipOrg,
|
||||||
|
...accessTokenTTLParams
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachUniversalAuth = async ({
|
const attachUniversalAuth = async ({
|
||||||
|
|||||||
@@ -40,14 +40,10 @@ To interact with various resources in Infisical, Machine Identities can authenti
|
|||||||
|
|
||||||
## Identity Lockout
|
## Identity Lockout
|
||||||
|
|
||||||
Lockout is a feature that prevents brute-force attacks on identity login endpoints. Auth methods that support lockout include: [Universal Auth](/documentation/platform/identities/universal-auth).
|
Lockout is a feature that prevents brute-force attacks on identity login endpoints. Auth methods that support lockout include: [Universal Auth](/documentation/platform/identities/universal-auth), [LDAP Auth](/documentation/platform/identities/ldap-auth/general).
|
||||||
|
|
||||||
Supported auth methods have lockout enabled by default. If triggered, lockout temporarily disables the login endpoint for 5 minutes after 3 consecutive failed login attempts within a 30-second window. Lockout can be configured and disabled in the identity auth method settings.
|
Supported auth methods have lockout enabled by default. If triggered, lockout temporarily disables the login endpoint for 5 minutes after 3 consecutive failed login attempts within a 30-second window. Lockout can be configured and disabled in the identity auth method settings.
|
||||||
|
|
||||||
<Warning>
|
|
||||||
When Lockout is enabled, a rate limit of approximately 10 requests per second is enforced on relevant authentication endpoints. This security measure employs a protective lock to mitigate parallel login attacks. If this rate limitation interferes with your operational requirements, you may consider disabling Lockout.
|
|
||||||
</Warning>
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ export const useMoveSecrets = ({
|
|||||||
destinationSecretPath,
|
destinationSecretPath,
|
||||||
secretIds,
|
secretIds,
|
||||||
shouldOverwrite,
|
shouldOverwrite,
|
||||||
projectId
|
projectSlug
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.post<{
|
const { data } = await apiRequest.post<{
|
||||||
isSourceUpdated: boolean;
|
isSourceUpdated: boolean;
|
||||||
@@ -349,7 +349,7 @@ export const useMoveSecrets = ({
|
|||||||
destinationSecretPath,
|
destinationSecretPath,
|
||||||
secretIds,
|
secretIds,
|
||||||
shouldOverwrite,
|
shouldOverwrite,
|
||||||
projectId
|
projectSlug
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ export type TDeleteSecretBatchDTO = {
|
|||||||
|
|
||||||
export type TMoveSecretsDTO = {
|
export type TMoveSecretsDTO = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
projectSlug: string;
|
||||||
sourceEnvironment: string;
|
sourceEnvironment: string;
|
||||||
sourceSecretPath: string;
|
sourceSecretPath: string;
|
||||||
destinationEnvironment: string;
|
destinationEnvironment: string;
|
||||||
|
|||||||
@@ -731,6 +731,9 @@ export const OverviewPage = () => {
|
|||||||
|
|
||||||
userAvailableEnvs.forEach((env) => {
|
userAvailableEnvs.forEach((env) => {
|
||||||
secrets?.forEach((secret) => {
|
secrets?.forEach((secret) => {
|
||||||
|
// bulk actions don't apply to rotation secrets (move/delete)
|
||||||
|
if (secret.isRotatedSecret) return;
|
||||||
|
|
||||||
if (allRowsSelectedOnPage.isChecked) {
|
if (allRowsSelectedOnPage.isChecked) {
|
||||||
delete newChecks[EntryType.SECRET][secret.key];
|
delete newChecks[EntryType.SECRET][secret.key];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+2
@@ -66,6 +66,7 @@ const Content = ({
|
|||||||
secrets,
|
secrets,
|
||||||
environments,
|
environments,
|
||||||
projectId,
|
projectId,
|
||||||
|
projectSlug,
|
||||||
sourceSecretPath
|
sourceSecretPath
|
||||||
}: ContentProps) => {
|
}: ContentProps) => {
|
||||||
const [search, setSearch] = useState(sourceSecretPath);
|
const [search, setSearch] = useState(sourceSecretPath);
|
||||||
@@ -194,6 +195,7 @@ const Content = ({
|
|||||||
destinationEnvironment: environment.slug,
|
destinationEnvironment: environment.slug,
|
||||||
destinationSecretPath: value.secretPath,
|
destinationSecretPath: value.secretPath,
|
||||||
projectId,
|
projectId,
|
||||||
|
projectSlug,
|
||||||
secretIds: secretsToMove.map((sec) => sec.id)
|
secretIds: secretsToMove.map((sec) => sec.id)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -570,6 +570,9 @@ const Page = () => {
|
|||||||
const newChecks = { ...selectedSecrets };
|
const newChecks = { ...selectedSecrets };
|
||||||
|
|
||||||
secrets?.forEach((secret) => {
|
secrets?.forEach((secret) => {
|
||||||
|
// bulk actions don't apply to rotation secrets (move/delete)
|
||||||
|
if (secret.isRotatedSecret) return;
|
||||||
|
|
||||||
if (allRowsSelectedOnPage.isChecked) {
|
if (allRowsSelectedOnPage.isChecked) {
|
||||||
delete newChecks[secret.id];
|
delete newChecks[secret.id];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+2
-4
@@ -72,10 +72,7 @@ import {
|
|||||||
useMoveSecrets,
|
useMoveSecrets,
|
||||||
useUpdateSecretBatch
|
useUpdateSecretBatch
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import {
|
import { dashboardKeys, fetchDashboardProjectSecretsByKeys } from "@app/hooks/api/dashboard/queries";
|
||||||
dashboardKeys,
|
|
||||||
fetchDashboardProjectSecretsByKeys
|
|
||||||
} from "@app/hooks/api/dashboard/queries";
|
|
||||||
import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types";
|
import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types";
|
||||||
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
|
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
|
||||||
import { PendingAction } from "@app/hooks/api/secretFolders/types";
|
import { PendingAction } from "@app/hooks/api/secretFolders/types";
|
||||||
@@ -349,6 +346,7 @@ export const ActionBar = ({
|
|||||||
destinationEnvironment,
|
destinationEnvironment,
|
||||||
destinationSecretPath,
|
destinationSecretPath,
|
||||||
projectId,
|
projectId,
|
||||||
|
projectSlug: currentProject.slug,
|
||||||
secretIds: secretsToMove.map((sec) => sec.id)
|
secretIds: secretsToMove.map((sec) => sec.id)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user