Files
zitadel/apps/login/src/lib/server/password.ts

277 lines
7.7 KiB
TypeScript
Raw Normal View History

"use server";
import {
createSessionAndUpdateCookie,
setSessionAndUpdateCookie,
} from "@/lib/server/cookie";
2024-09-18 14:13:04 +02:00
import {
2024-10-16 11:20:23 +02:00
getUserByID,
2024-09-18 14:13:04 +02:00
listAuthenticationMethodTypes,
listUsers,
passwordReset,
2024-10-16 11:20:23 +02:00
setPassword,
2024-09-18 14:13:04 +02:00
} from "@/lib/zitadel";
import { create } from "@zitadel/client";
import {
Checks,
ChecksSchema,
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
2024-09-18 14:13:04 +02:00
import { getSessionCookieByLoginName } from "../cookies";
type ResetPasswordCommand = {
loginName: string;
organization?: string;
};
export async function resetPassword(command: ResetPasswordCommand) {
const host = headers().get("host");
const users = await listUsers({
2024-09-11 09:27:04 +02:00
loginName: command.loginName,
2024-09-05 13:38:03 +02:00
organizationId: command.organization,
});
if (
!users.details ||
2024-09-10 13:54:09 +02:00
users.details.totalResult !== BigInt(1) ||
!users.result[0].userId
) {
2024-09-19 09:16:11 +02:00
return { error: "Could not send Password Reset Link" };
}
const userId = users.result[0].userId;
return passwordReset(userId, host);
}
2024-09-18 14:13:04 +02:00
export type UpdateSessionCommand = {
loginName: string;
organization?: string;
checks: Checks;
authRequestId?: string;
forceMfa?: boolean;
2024-09-18 14:13:04 +02:00
};
export async function sendPassword(command: UpdateSessionCommand) {
let sessionCookie = await getSessionCookieByLoginName({
loginName: command.loginName,
organization: command.organization,
2024-09-18 15:57:26 +02:00
}).catch((error) => {
console.warn("Ignored error:", error);
2024-09-18 14:13:04 +02:00
});
let session;
let user: User;
2024-09-18 14:13:04 +02:00
if (!sessionCookie) {
const users = await listUsers({
loginName: command.loginName,
organizationId: command.organization,
});
if (users.details?.totalResult == BigInt(1) && users.result[0].userId) {
user = users.result[0];
2024-09-18 14:13:04 +02:00
const checks = create(ChecksSchema, {
user: { search: { case: "userId", value: users.result[0].userId } },
password: { password: command.checks.password?.password },
});
session = await createSessionAndUpdateCookie(
checks,
undefined,
command.authRequestId,
);
}
// this is a fake error message to hide that the user does not even exist
return { error: "Could not verify password" };
2024-09-18 14:13:04 +02:00
} else {
2024-09-18 15:57:26 +02:00
session = await setSessionAndUpdateCookie(
2024-09-18 14:13:04 +02:00
sessionCookie,
command.checks,
undefined,
command.authRequestId,
);
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };
}
const userResponse = await getUserByID(session?.factors?.user?.id);
if (!userResponse.user) {
return { error: "Could not find user" };
}
user = userResponse.user;
2024-09-18 15:57:26 +02:00
}
2024-09-18 14:13:04 +02:00
2024-09-18 15:57:26 +02:00
if (!session?.factors?.user?.id || !sessionCookie) {
return { error: "Could not create session for user" };
}
2024-09-18 14:13:04 +02:00
2024-09-18 15:57:26 +02:00
// if password, check if user has MFA methods
let authMethods;
if (command.checks && command.checks.password && session.factors?.user?.id) {
const response = await listAuthenticationMethodTypes(
session.factors.user.id,
);
if (response.authMethodTypes && response.authMethodTypes.length) {
authMethods = response.authMethodTypes;
}
2024-09-18 14:13:04 +02:00
}
2024-09-18 15:57:26 +02:00
2024-11-12 12:17:16 +01:00
if (!authMethods || !session.factors?.user?.loginName) {
return { error: "Could not verify password!" };
}
2024-11-12 12:17:16 +01:00
const availableSecondFactors = authMethods?.filter(
(m: AuthenticationMethodType) =>
m !== AuthenticationMethodType.PASSWORD &&
m !== AuthenticationMethodType.PASSKEY,
);
if (availableSecondFactors?.length == 1) {
const params = new URLSearchParams({
2024-11-12 12:17:16 +01:00
loginName: session.factors?.user.loginName,
});
if (command.authRequestId) {
params.append("authRequestId", command.authRequestId);
}
if (command.organization) {
params.append("organization", command.organization);
}
const factor = availableSecondFactors[0];
// if passwordless is other method, but user selected password as alternative, perform a login
if (factor === AuthenticationMethodType.TOTP) {
return redirect(`/otp/time-based?` + params);
} else if (factor === AuthenticationMethodType.OTP_SMS) {
return redirect(`/otp/sms?` + params);
} else if (factor === AuthenticationMethodType.OTP_EMAIL) {
return redirect(`/otp/email?` + params);
} else if (factor === AuthenticationMethodType.U2F) {
return redirect(`/u2f?` + params);
}
} else if (availableSecondFactors?.length >= 1) {
const params = new URLSearchParams({
2024-11-12 12:17:16 +01:00
loginName: session.factors.user.loginName,
});
if (command.authRequestId) {
params.append("authRequestId", command.authRequestId);
}
if (command.organization) {
params.append("organization", command.organization);
}
return redirect(`/mfa?` + params);
2024-11-12 12:17:16 +01:00
} else if (user.state === UserState.INITIAL) {
const params = new URLSearchParams({
2024-11-12 12:17:16 +01:00
loginName: session.factors.user.loginName,
});
if (command.authRequestId) {
params.append("authRequestId", command.authRequestId);
}
if (command.organization) {
params.append("organization", command.organization);
}
return redirect(`/password/change?` + params);
} else if (command.forceMfa && !availableSecondFactors.length) {
const params = new URLSearchParams({
2024-11-12 12:17:16 +01:00
loginName: session.factors.user.loginName,
force: "true", // this defines if the mfa is forced in the settings
checkAfter: "true", // this defines if the check is directly made after the setup
});
if (command.authRequestId) {
params.append("authRequestId", command.authRequestId);
}
if (command.organization) {
params.append("organization", command.organization);
}
// TODO: provide a way to setup passkeys on mfa page?
return redirect(`/mfa/set?` + params);
}
// TODO: implement passkey setup
// else if (
// submitted.factors &&
// !submitted.factors.webAuthN && // if session was not verified with a passkey
// promptPasswordless && // if explicitly prompted due policy
// !isAlternative // escaped if password was used as an alternative method
// ) {
// const params = new URLSearchParams({
// loginName: submitted.factors.user.loginName,
// prompt: "true",
// });
// if (authRequestId) {
// params.append("authRequestId", authRequestId);
// }
// if (organization) {
// params.append("organization", organization);
// }
// return router.push(`/passkey/set?` + params);
// }
2024-11-12 12:17:16 +01:00
else if (command.authRequestId && session.id) {
const params = new URLSearchParams({
2024-11-12 12:17:16 +01:00
sessionId: session.id,
authRequest: command.authRequestId,
});
if (command.organization) {
params.append("organization", command.organization);
}
2024-11-12 12:07:31 +01:00
return { nextStep: `/login?${params}` };
}
// without OIDC flow
const params = new URLSearchParams(
command.authRequestId
? {
2024-11-12 12:17:16 +01:00
loginName: session.factors.user.loginName,
authRequestId: command.authRequestId,
}
: {
2024-11-12 12:17:16 +01:00
loginName: session.factors.user.loginName,
},
);
if (command.organization) {
params.append("organization", command.organization);
}
return redirect(`/signedin?` + params);
2024-09-18 14:13:04 +02:00
}
2024-10-15 17:27:08 +02:00
export async function changePassword(command: {
code?: string;
2024-10-15 17:27:08 +02:00
userId: string;
password: string;
}) {
// check for init state
2024-10-16 11:20:23 +02:00
const { user } = await getUserByID(command.userId);
2024-10-15 17:27:08 +02:00
2024-10-16 11:20:23 +02:00
if (!user || user.userId !== command.userId) {
2024-10-15 17:27:08 +02:00
return { error: "Could not send Password Reset Link" };
}
2024-10-16 11:20:23 +02:00
const userId = user.userId;
2024-10-15 17:27:08 +02:00
2024-10-23 14:28:33 +02:00
return setPassword(userId, command.password, user, command.code);
2024-10-15 17:27:08 +02:00
}