feat: reverted lockout in login completely

This commit is contained in:
=
2025-08-30 20:39:37 +05:30
parent 1d5cdb4000
commit 3fa6154517
@@ -45,10 +45,10 @@ type TIdentityUaServiceFactoryDep = {
export type TIdentityUaServiceFactory = ReturnType<typeof identityUaServiceFactory>; export type TIdentityUaServiceFactory = ReturnType<typeof identityUaServiceFactory>;
type LockoutObject = { // type LockoutObject = {
lockedOut: boolean; // lockedOut: boolean;
failedAttempts: number; // failedAttempts: number;
}; // };
export const identityUaServiceFactory = ({ export const identityUaServiceFactory = ({
identityUaDAL, identityUaDAL,
@@ -62,8 +62,15 @@ export const identityUaServiceFactory = ({
const login = async (clientId: string, clientSecret: string, ip: string) => { const login = async (clientId: string, clientSecret: string, ip: string) => {
const identityUa = await identityUaDAL.findOne({ clientId }); const identityUa = await identityUaDAL.findOne({ clientId });
if (!identityUa) { if (!identityUa) {
throw new UnauthorizedError({ throw new NotFoundError({
message: "Invalid credentials" message: "No identity with specified client ID was found"
});
}
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityUa.identityId });
if (!identityMembershipOrg) {
throw new NotFoundError({
message: "No identity with the org membership was found"
}); });
} }
@@ -71,42 +78,15 @@ export const identityUaServiceFactory = ({
ipAddress: ip, ipAddress: ip,
trustedIps: identityUa.clientSecretTrustedIps as TIp[] trustedIps: identityUa.clientSecretTrustedIps as TIp[]
}); });
const LOCKOUT_KEY = `lockout:identity:${identityUa.identityId}:${IdentityAuthMethod.UNIVERSAL_AUTH}:${clientId}`;
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityUa.identityId });
if (!identityMembershipOrg) {
throw new UnauthorizedError({
message: "Invalid credentials"
});
}
const identityTx = await identityUaDAL.transaction(async (tx) => {
// await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.IdentityLogin(identityUa.identityId, clientId)]);
// Lockout Check
const lockoutRaw = await keyStore.getItem(LOCKOUT_KEY);
let lockout: LockoutObject | undefined;
if (lockoutRaw) {
lockout = JSON.parse(lockoutRaw) as LockoutObject;
}
if (lockout && lockout.lockedOut) {
throw new UnauthorizedError({
message: "This identity auth method is temporarily locked, please try again later"
});
}
const clientSecretPrefix = clientSecret.slice(0, 4); const clientSecretPrefix = clientSecret.slice(0, 4);
const clientSecretInfo = await identityUaClientSecretDAL.find({ const clientSecrtInfo = await identityUaClientSecretDAL.find({
identityUAId: identityUa.id, identityUAId: identityUa.id,
isClientSecretRevoked: false, isClientSecretRevoked: false,
clientSecretPrefix clientSecretPrefix
}); });
let validClientSecretInfo: (typeof clientSecretInfo)[0] | null = null; let validClientSecretInfo: (typeof clientSecrtInfo)[0] | null = null;
for await (const info of clientSecretInfo) { for await (const info of clientSecrtInfo) {
const isMatch = await crypto.hashing().compareHash(clientSecret, info.clientSecretHash); const isMatch = await crypto.hashing().compareHash(clientSecret, info.clientSecretHash);
if (isMatch) { if (isMatch) {
@@ -115,31 +95,7 @@ export const identityUaServiceFactory = ({
} }
} }
if (!validClientSecretInfo) { if (!validClientSecretInfo) throw new UnauthorizedError({ message: "Invalid credentials" });
if (identityUa.lockoutEnabled) {
if (!lockout) {
lockout = {
lockedOut: false,
failedAttempts: 0
};
}
lockout.failedAttempts += 1;
if (lockout.failedAttempts >= identityUa.lockoutThreshold) {
lockout.lockedOut = true;
}
await keyStore.setItemWithExpiry(
LOCKOUT_KEY,
lockout.lockedOut ? identityUa.lockoutDurationSeconds : identityUa.lockoutCounterResetSeconds,
JSON.stringify(lockout)
);
}
throw new UnauthorizedError({ message: "Invalid credentials" });
} else if (lockout) {
await keyStore.deleteItem(LOCKOUT_KEY);
}
const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo; const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo;
if (Number(clientSecretTTL) > 0) { if (Number(clientSecretTTL) > 0) {
@@ -183,7 +139,8 @@ export const identityUaServiceFactory = ({
accessTokenMaxTTL: 1000000000 accessTokenMaxTTL: 1000000000
}; };
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo.id, tx); const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx);
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
@@ -206,33 +163,33 @@ export const identityUaServiceFactory = ({
tx tx
); );
return { newToken, validClientSecretInfo, accessTokenTTLParams }; return newToken;
}); });
const appCfg = getConfig(); const appCfg = getConfig();
const accessToken = crypto.jwt().sign( const accessToken = crypto.jwt().sign(
{ {
identityId: identityUa.identityId, identityId: identityUa.identityId,
clientSecretId: identityTx.validClientSecretInfo.id, clientSecretId: validClientSecretInfo.id,
identityAccessTokenId: identityTx.newToken.id, identityAccessTokenId: identityAccessToken.id,
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
} as TIdentityAccessTokenJwtPayload, } as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET, appCfg.AUTH_SECRET,
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error // akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
Number(identityTx.newToken.accessTokenTTL) === 0 Number(identityAccessToken.accessTokenTTL) === 0
? undefined ? undefined
: { : {
expiresIn: Number(identityTx.newToken.accessTokenTTL) expiresIn: Number(identityAccessToken.accessTokenTTL)
} }
); );
return { return {
accessToken, accessToken,
identityUa, identityUa,
validClientSecretInfo: identityTx.validClientSecretInfo, validClientSecretInfo,
identityAccessToken: identityTx.newToken, identityAccessToken,
identityMembershipOrg, identityMembershipOrg,
...identityTx.accessTokenTTLParams ...accessTokenTTLParams
}; };
}; };