Swap to a single service function with a callback

This commit is contained in:
x032205
2025-09-09 17:22:27 -04:00
parent 530c6476b2
commit 4cb583d76c
4 changed files with 68 additions and 96 deletions
+2 -1
View File
@@ -1,9 +1,10 @@
import { Cluster, Redis } from "ioredis";
import { buildRedisFromConfig, TRedisConfigKeys } from "@app/lib/config/redis"; import { buildRedisFromConfig, TRedisConfigKeys } from "@app/lib/config/redis";
import { pgAdvisoryLockHashText } from "@app/lib/crypto/hashtext"; import { pgAdvisoryLockHashText } from "@app/lib/crypto/hashtext";
import { applyJitter } from "@app/lib/dates"; import { applyJitter } from "@app/lib/dates";
import { delay as delayMs } from "@app/lib/delay"; import { delay as delayMs } from "@app/lib/delay";
import { ExecutionResult, Redlock, Settings } from "@app/lib/red-lock"; import { ExecutionResult, Redlock, Settings } from "@app/lib/red-lock";
import { Redis, Cluster } from "ioredis";
export const PgSqlLock = { export const PgSqlLock = {
BootUpMigration: 2023, BootUpMigration: 2023,
@@ -8,7 +8,7 @@
import { Authenticator } from "@fastify/passport"; import { Authenticator } from "@fastify/passport";
import fastifySession from "@fastify/session"; import fastifySession from "@fastify/session";
import { FastifyRequest } from "fastify"; import { FastifyReply, FastifyRequest } from "fastify";
import { IncomingMessage } from "http"; import { IncomingMessage } from "http";
import LdapStrategy from "passport-ldapauth"; import LdapStrategy from "passport-ldapauth";
import { z } from "zod"; import { z } from "zod";
@@ -136,40 +136,23 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
} }
}, },
preValidation: [ preValidation: [
async (req, res) => { (req, res) => {
const { lock } = await server.services.identityLdapAuth.checkLdapLockout({ const passportAuth = (request: FastifyRequest, reply: FastifyReply) =>
identityId: req.body.identityId, (
username: req.body.username
});
try {
const passportRes = await (
passport.authenticate("ldapauth", { passport.authenticate("ldapauth", {
failWithError: true, failWithError: true,
session: false session: false
}) as any }) as any
)(req, res); )(request, reply);
await server.services.identityLdapAuth.resetLdapLockoutCounter({ const { identityId, username } = req.body;
identityId: req.body.identityId, return server.services.identityLdapAuth.withLdapLockout(
username: req.body.username {
}); identityId,
username
return passportRes; },
} catch (error) { () => passportAuth(req, res)
if ((error as any).status === 401) { );
await server.services.identityLdapAuth.incrementLdapLockout({
identityId: req.body.identityId,
username: req.body.username
});
throw new UnauthorizedError({ message: "Invalid credentials" });
}
throw error;
} finally {
await lock.release();
}
} }
], ],
handler: async (req) => { handler: async (req) => {
@@ -43,9 +43,7 @@ import {
TCheckLdapAuthLockoutDTO, TCheckLdapAuthLockoutDTO,
TClearLdapAuthLockoutsDTO, TClearLdapAuthLockoutsDTO,
TGetLdapAuthDTO, TGetLdapAuthDTO,
TIncrementLdapAuthLockoutDTO,
TLoginLdapAuthDTO, TLoginLdapAuthDTO,
TResetLdapAuthLockoutCounterDTO,
TRevokeLdapAuthDTO, TRevokeLdapAuthDTO,
TUpdateLdapAuthDTO TUpdateLdapAuthDTO
} from "./identity-ldap-auth-types"; } from "./identity-ldap-auth-types";
@@ -653,7 +651,10 @@ export const identityLdapAuthServiceFactory = ({
return revokedIdentityLdapAuth; return revokedIdentityLdapAuth;
}; };
const checkLdapLockout = async ({ identityId, username }: TCheckLdapAuthLockoutDTO) => { const withLdapLockout = async <T>(
{ identityId, username }: TCheckLdapAuthLockoutDTO,
authFn: () => Promise<T>
): Promise<T> => {
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>>; let lock: Awaited<ReturnType<typeof keyStore.acquireLock>>;
@@ -667,36 +668,34 @@ export const identityLdapAuthServiceFactory = ({
logger.info( logger.info(
`identity login failed to acquire lock [identityId=${identityId}] [authMethod=${IdentityAuthMethod.LDAP_AUTH}]` `identity login failed to acquire lock [identityId=${identityId}] [authMethod=${IdentityAuthMethod.LDAP_AUTH}]`
); );
throw new RateLimitError({ message: "Rate limit exceeded" }); throw new RateLimitError({ message: "Failed to acquire lock: rate limit exceeded" });
} }
try {
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 result = await authFn();
};
const incrementLdapLockout = async ({ identityId, username }: TIncrementLdapAuthLockoutDTO) => { await keyStore.deleteItem(LOCKOUT_KEY);
return result;
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
if ((error as any).status === 401) {
const identityLdapAuth = await identityLdapAuthDAL.findOne({ identityId }); const identityLdapAuth = await identityLdapAuthDAL.findOne({ identityId });
if (!identityLdapAuth) { if (!identityLdapAuth) {
throw new UnauthorizedError({ throw new UnauthorizedError({ message: "Invalid credentials" });
message: "Invalid credentials"
});
} }
if (identityLdapAuth.lockoutEnabled) { if (identityLdapAuth.lockoutEnabled) {
const LOCKOUT_KEY = `lockout:identity:${identityId}:${IdentityAuthMethod.LDAP_AUTH}:${username.trim().toLowerCase()}`;
let lockout: LockoutObject = { let lockout: LockoutObject = {
lockedOut: false, lockedOut: false,
failedAttempts: 0 failedAttempts: 0
@@ -718,12 +717,13 @@ export const identityLdapAuthServiceFactory = ({
JSON.stringify(lockout) JSON.stringify(lockout)
); );
} }
};
const resetLdapLockoutCounter = async ({ identityId, username }: TResetLdapAuthLockoutCounterDTO) => { throw new UnauthorizedError({ message: "Invalid credentials" });
await keyStore.deleteItem( }
`lockout:identity:${identityId}:${IdentityAuthMethod.LDAP_AUTH}:${username.trim().toLowerCase()}` throw error;
); } finally {
await lock.release();
}
}; };
const clearLdapAuthLockouts = async ({ const clearLdapAuthLockouts = async ({
@@ -765,9 +765,7 @@ export const identityLdapAuthServiceFactory = ({
login, login,
revokeIdentityLdapAuth, revokeIdentityLdapAuth,
getLdapAuth, getLdapAuth,
checkLdapLockout, withLdapLockout,
incrementLdapLockout,
resetLdapLockoutCounter,
clearLdapAuthLockouts clearLdapAuthLockouts
}; };
}; };
@@ -73,13 +73,3 @@ export type TCheckLdapAuthLockoutDTO = {
identityId: string; identityId: string;
username: string; username: string;
}; };
export type TIncrementLdapAuthLockoutDTO = {
identityId: string;
username: string;
};
export type TResetLdapAuthLockoutCounterDTO = {
identityId: string;
username: string;
};