mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-09 05:28:23 +00:00
lock to prevent parallel logins
This commit is contained in:
@@ -137,7 +137,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
|
|||||||
},
|
},
|
||||||
preValidation: [
|
preValidation: [
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
await server.services.identityLdapAuth.checkLdapLockout({
|
const { lock } = await server.services.identityLdapAuth.checkLdapLockout({
|
||||||
identityId: req.body.identityId,
|
identityId: req.body.identityId,
|
||||||
username: req.body.username
|
username: req.body.username
|
||||||
});
|
});
|
||||||
@@ -167,6 +167,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -15,11 +15,18 @@ import {
|
|||||||
validatePrivilegeChangeOperation
|
validatePrivilegeChangeOperation
|
||||||
} from "@app/ee/services/permission/permission-fns";
|
} from "@app/ee/services/permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
NotFoundError,
|
||||||
|
PermissionBoundaryError,
|
||||||
|
RateLimitError,
|
||||||
|
UnauthorizedError
|
||||||
|
} from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||||
import { TIdentityDALFactory } from "../identity/identity-dal";
|
import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||||
@@ -55,7 +62,10 @@ type TIdentityLdapAuthServiceFactoryDep = {
|
|||||||
kmsService: TKmsServiceFactory;
|
kmsService: TKmsServiceFactory;
|
||||||
identityDAL: TIdentityDALFactory;
|
identityDAL: TIdentityDALFactory;
|
||||||
identityAuthTemplateDAL: TIdentityAuthTemplateDALFactory;
|
identityAuthTemplateDAL: TIdentityAuthTemplateDALFactory;
|
||||||
keyStore: Pick<TKeyStoreFactory, "setItemWithExpiry" | "getItem" | "deleteItem" | "getKeysByPattern" | "deleteItems">;
|
keyStore: Pick<
|
||||||
|
TKeyStoreFactory,
|
||||||
|
"setItemWithExpiry" | "getItem" | "deleteItem" | "getKeysByPattern" | "deleteItems" | "acquireLock"
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIdentityLdapAuthServiceFactory = ReturnType<typeof identityLdapAuthServiceFactory>;
|
export type TIdentityLdapAuthServiceFactory = ReturnType<typeof identityLdapAuthServiceFactory>;
|
||||||
@@ -646,17 +656,34 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
const checkLdapLockout = async ({ identityId, username }: TCheckLdapAuthLockoutDTO) => {
|
const checkLdapLockout = async ({ identityId, username }: TCheckLdapAuthLockoutDTO) => {
|
||||||
const LOCKOUT_KEY = `lockout:identity:${identityId}:${IdentityAuthMethod.LDAP_AUTH}:${username.trim().toLowerCase()}`;
|
const LOCKOUT_KEY = `lockout:identity:${identityId}:${IdentityAuthMethod.LDAP_AUTH}:${username.trim().toLowerCase()}`;
|
||||||
|
|
||||||
|
let lock: Awaited<ReturnType<typeof keyStore.acquireLock>>;
|
||||||
|
try {
|
||||||
|
lock = await keyStore.acquireLock([KeyStorePrefixes.IdentityLockoutLock(LOCKOUT_KEY)], 3000, {
|
||||||
|
retryCount: 3,
|
||||||
|
retryDelay: 1500,
|
||||||
|
retryJitter: 100
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.info(
|
||||||
|
`identity login failed to acquire lock [identityId=${identityId}] [authMethod=${IdentityAuthMethod.LDAP_AUTH}]`
|
||||||
|
);
|
||||||
|
throw new RateLimitError({ message: "Rate limit exceeded" });
|
||||||
|
}
|
||||||
|
|
||||||
const lockoutRaw = await keyStore.getItem(LOCKOUT_KEY);
|
const lockoutRaw = await keyStore.getItem(LOCKOUT_KEY);
|
||||||
|
|
||||||
if (lockoutRaw) {
|
if (lockoutRaw) {
|
||||||
const lockout = JSON.parse(lockoutRaw) as LockoutObject;
|
const lockout = JSON.parse(lockoutRaw) as LockoutObject;
|
||||||
|
|
||||||
if (lockout.lockedOut) {
|
if (lockout.lockedOut) {
|
||||||
|
await lock.release();
|
||||||
throw new UnauthorizedError({
|
throw new UnauthorizedError({
|
||||||
message: "This identity auth method is temporarily locked, please try again later"
|
message: "This identity auth method is temporarily locked, please try again later"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { lock };
|
||||||
};
|
};
|
||||||
|
|
||||||
const incrementLdapLockout = async ({ identityId, username }: TIncrementLdapAuthLockoutDTO) => {
|
const incrementLdapLockout = async ({ identityId, username }: TIncrementLdapAuthLockoutDTO) => {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type TIdentityServiceFactoryDep = {
|
|||||||
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findByIdentityId">;
|
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findByIdentityId">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||||
keyStore: Pick<TKeyStoreFactory, "getKeysByPattern">;
|
keyStore: Pick<TKeyStoreFactory, "getKeysByPattern" | "getItem">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIdentityServiceFactory = ReturnType<typeof identityServiceFactory>;
|
export type TIdentityServiceFactory = ReturnType<typeof identityServiceFactory>;
|
||||||
@@ -261,12 +261,18 @@ export const identityServiceFactory = ({
|
|||||||
const activeLockouts = await keyStore.getKeysByPattern(`lockout:identity:${id}:*`);
|
const activeLockouts = await keyStore.getKeysByPattern(`lockout:identity:${id}:*`);
|
||||||
|
|
||||||
const activeLockoutAuthMethods = new Set<string>();
|
const activeLockoutAuthMethods = new Set<string>();
|
||||||
activeLockouts.forEach((key) => {
|
for await (const key of activeLockouts) {
|
||||||
const parts = key.split(":");
|
const parts = key.split(":");
|
||||||
if (parts.length > 3) {
|
if (parts.length > 3) {
|
||||||
activeLockoutAuthMethods.add(parts[3]);
|
const lockoutRaw = await keyStore.getItem(key);
|
||||||
|
if (lockoutRaw) {
|
||||||
|
const lockout = JSON.parse(lockoutRaw) as { lockedOut: boolean };
|
||||||
|
if (lockout.lockedOut) {
|
||||||
|
activeLockoutAuthMethods.add(parts[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...identity,
|
...identity,
|
||||||
|
|||||||
Reference in New Issue
Block a user