handle password attempts error

This commit is contained in:
Max Peintner
2025-01-27 14:15:05 +01:00
parent b93035eeb1
commit 63656e16fb
4 changed files with 123 additions and 66 deletions

View File

@@ -8,6 +8,10 @@ import {
setSession,
} from "@/lib/zitadel";
import { Duration, timestampMs } from "@zitadel/client";
import {
CredentialsCheckError,
ErrorDetail,
} from "@zitadel/proto/zitadel/message_pb";
import {
Challenges,
RequestChallenges,
@@ -89,7 +93,16 @@ export async function createSessionForIdpAndUpdateCookie(
userId,
idpIntent,
lifetime,
);
).catch((error: ErrorDetail | CredentialsCheckError) => {
console.error("Could not set session", error);
if ("failedAttempts" in error && error.failedAttempts) {
throw {
error: `Failed to authenticate: You had ${error.failedAttempts} password attempts.`,
failedAttempts: error.failedAttempts,
};
}
throw error;
});
if (!createdSession) {
throw "Could not create session";
@@ -148,57 +161,68 @@ export async function setSessionAndUpdateCookie(
challenges,
checks,
lifetime,
).then((updatedSession) => {
if (updatedSession) {
const sessionCookie: CustomCookieData = {
id: recentCookie.id,
token: updatedSession.sessionToken,
creationTs: recentCookie.creationTs,
expirationTs: recentCookie.expirationTs,
// just overwrite the changeDate with the new one
changeTs: updatedSession.details?.changeDate
? `${timestampMs(updatedSession.details.changeDate)}`
: "",
loginName: recentCookie.loginName,
organization: recentCookie.organization,
};
)
.then((updatedSession) => {
if (updatedSession) {
const sessionCookie: CustomCookieData = {
id: recentCookie.id,
token: updatedSession.sessionToken,
creationTs: recentCookie.creationTs,
expirationTs: recentCookie.expirationTs,
// just overwrite the changeDate with the new one
changeTs: updatedSession.details?.changeDate
? `${timestampMs(updatedSession.details.changeDate)}`
: "",
loginName: recentCookie.loginName,
organization: recentCookie.organization,
};
if (authRequestId) {
sessionCookie.authRequestId = authRequestId;
}
return getSession({
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
}).then((response) => {
if (response?.session && response.session.factors?.user?.loginName) {
const { session } = response;
const newCookie: CustomCookieData = {
id: sessionCookie.id,
token: updatedSession.sessionToken,
creationTs: sessionCookie.creationTs,
expirationTs: sessionCookie.expirationTs,
// just overwrite the changeDate with the new one
changeTs: updatedSession.details?.changeDate
? `${timestampMs(updatedSession.details.changeDate)}`
: "",
loginName: session.factors?.user?.loginName ?? "",
organization: session.factors?.user?.organizationId ?? "",
};
if (sessionCookie.authRequestId) {
newCookie.authRequestId = sessionCookie.authRequestId;
}
return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
return { challenges: updatedSession.challenges, ...session };
});
} else {
throw "could not get session or session does not have loginName";
if (authRequestId) {
sessionCookie.authRequestId = authRequestId;
}
});
} else {
throw "Session not be set";
}
});
return getSession({
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
}).then((response) => {
if (response?.session && response.session.factors?.user?.loginName) {
const { session } = response;
const newCookie: CustomCookieData = {
id: sessionCookie.id,
token: updatedSession.sessionToken,
creationTs: sessionCookie.creationTs,
expirationTs: sessionCookie.expirationTs,
// just overwrite the changeDate with the new one
changeTs: updatedSession.details?.changeDate
? `${timestampMs(updatedSession.details.changeDate)}`
: "",
loginName: session.factors?.user?.loginName ?? "",
organization: session.factors?.user?.organizationId ?? "",
};
if (sessionCookie.authRequestId) {
newCookie.authRequestId = sessionCookie.authRequestId;
}
return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
return { challenges: updatedSession.challenges, ...session };
});
} else {
throw "could not get session or session does not have loginName";
}
});
} else {
throw "Session not be set";
}
})
.catch((error: ErrorDetail | CredentialsCheckError) => {
console.error("Could not set session", error);
if ("failedAttempts" in error && error.failedAttempts) {
throw {
error: `Failed to authenticate: You had ${error.failedAttempts} password attempts.`,
failedAttempts: error.failedAttempts,
};
}
throw error;
});
}

View File

@@ -5,6 +5,7 @@ import {
setSessionAndUpdateCookie,
} from "@/lib/server/cookie";
import {
getLockoutSettings,
getLoginSettings,
getPasswordExpirySettings,
getSession,
@@ -98,24 +99,48 @@ export async function sendPassword(command: UpdateSessionCommand) {
loginSettings = await getLoginSettings(command.organization);
session = await createSessionAndUpdateCookie(
checks,
undefined,
command.authRequestId,
loginSettings?.passwordCheckLifetime,
);
try {
session = await createSessionAndUpdateCookie(
checks,
undefined,
command.authRequestId,
loginSettings?.passwordCheckLifetime,
);
} catch (error: any) {
if ("failedAttempts" in error && error.failedAttempts) {
const lockoutSettings = await getLockoutSettings(
command.organization,
);
return {
error: `Failed to authenticate: You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.`,
};
}
return { error: "Could not create session for user" };
}
}
// this is a fake error message to hide that the user does not even exist
return { error: "Could not verify password" };
} else {
session = await setSessionAndUpdateCookie(
sessionCookie,
command.checks,
undefined,
command.authRequestId,
loginSettings?.passwordCheckLifetime,
);
try {
session = await setSessionAndUpdateCookie(
sessionCookie,
command.checks,
undefined,
command.authRequestId,
loginSettings?.passwordCheckLifetime,
);
} catch (error: any) {
if ("failedAttempts" in error && error.failedAttempts) {
const lockoutSettings = await getLockoutSettings(command.organization);
return {
error: `Failed to authenticate: You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.`,
};
}
throw error;
}
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };

View File

@@ -81,6 +81,14 @@ export async function getLoginSettings(orgId?: string) {
return useCache ? cacheWrapper(callback) : callback;
}
export async function getLockoutSettings(orgId?: string) {
const callback = settingsService
.getLockoutSettings({ ctx: makeReqCtx(orgId) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
return useCache ? cacheWrapper(callback) : callback;
}
export async function getPasswordExpirySettings(orgId?: string) {
const callback = settingsService
.getPasswordExpirySettings({ ctx: makeReqCtx(orgId) }, {})

View File

@@ -14,7 +14,7 @@
],
"sideEffects": false,
"scripts": {
"generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel",
"generate": "buf generate https://github.com/zitadel/zitadel.git#branch=fix/9198-user_password_lockout_error_response --path ./proto/zitadel",
"clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate"
},
"dependencies": {