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

335 lines
8.6 KiB
TypeScript
Raw Normal View History

2024-12-17 15:57:42 +01:00
"use server";
import {
getLoginSettings,
2024-12-19 08:59:39 +01:00
getSession,
2024-12-17 15:57:42 +01:00
getUserByID,
listAuthenticationMethodTypes,
resendEmailCode,
resendInviteCode,
verifyEmail,
verifyInviteCode,
} from "@/lib/zitadel";
import { create } from "@zitadel/client";
2024-12-19 08:59:39 +01:00
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
2024-12-17 15:57:42 +01:00
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { User } from "@zitadel/proto/zitadel/user/v2/user_pb";
2024-12-19 11:28:35 +01:00
import { headers } from "next/headers";
2024-12-19 08:59:39 +01:00
import { getNextUrl } from "../client";
2024-12-17 15:57:42 +01:00
import { getSessionCookieByLoginName } from "../cookies";
2024-12-19 11:28:35 +01:00
import { checkMFAFactors } from "../verify-helper";
2024-12-19 08:59:39 +01:00
import { createSessionAndUpdateCookie } from "./cookie";
2024-12-17 15:57:42 +01:00
type VerifyUserByEmailCommand = {
userId: string;
2024-12-19 11:28:35 +01:00
loginName?: string; // to determine already existing session
organization?: string;
2024-12-17 15:57:42 +01:00
code: string;
isInvite: boolean;
authRequestId?: string;
};
export async function sendVerification(command: VerifyUserByEmailCommand) {
const verifyResponse = command.isInvite
? await verifyInviteCode(command.userId, command.code).catch(() => {
return { error: "Could not verify invite" };
})
: await verifyEmail(command.userId, command.code).catch(() => {
return { error: "Could not verify email" };
});
if (!verifyResponse) {
return { error: "Could not verify user" };
}
2024-12-19 11:28:35 +01:00
let session: Session | undefined;
let user: User | undefined;
2024-12-17 15:57:42 +01:00
2024-12-19 11:28:35 +01:00
if ("loginName" in command) {
const sessionCookie = await getSessionCookieByLoginName({
loginName: command.loginName,
organization: command.organization,
}).catch((error) => {
console.warn("Ignored error:", error);
});
if (!sessionCookie) {
return { error: "Could not load session cookie" };
}
session = await getSession({
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
}).then((response) => {
if (response?.session) {
return response.session;
}
});
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };
}
2024-12-17 15:57:42 +01:00
2024-12-19 11:28:35 +01:00
const userResponse = await getUserByID(session?.factors?.user?.id);
if (!userResponse?.user) {
return { error: "Could not load user" };
}
user = userResponse.user;
} else {
const userResponse = await getUserByID(command.userId);
if (!userResponse || !userResponse.user) {
return { error: "Could not load user" };
}
user = userResponse.user;
const checks = create(ChecksSchema, {
user: {
search: {
case: "loginName",
value: userResponse.user.preferredLoginName,
},
2024-12-17 15:57:42 +01:00
},
2024-12-19 11:28:35 +01:00
});
2024-12-17 15:57:42 +01:00
2024-12-19 11:28:35 +01:00
session = await createSessionAndUpdateCookie(
checks,
undefined,
command.authRequestId,
);
}
2024-12-17 15:57:42 +01:00
2024-12-19 11:28:35 +01:00
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };
}
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };
}
if (!user) {
return { error: "Could not load user" };
}
const loginSettings = await getLoginSettings(user.details?.resourceOwner);
const authMethodResponse = await listAuthenticationMethodTypes(user.userId);
2024-12-17 15:57:42 +01:00
if (!authMethodResponse || !authMethodResponse.authMethodTypes) {
return { error: "Could not load possible authenticators" };
}
2024-12-19 11:28:35 +01:00
2024-12-17 15:57:42 +01:00
// if no authmethods are found on the user, redirect to set one up
if (
authMethodResponse &&
authMethodResponse.authMethodTypes &&
authMethodResponse.authMethodTypes.length == 0
) {
const params = new URLSearchParams({
sessionId: session.id,
});
if (session.factors?.user?.loginName) {
params.set("loginName", session.factors?.user?.loginName);
}
return { redirect: `/authenticator/set?${params}` };
}
2024-12-19 11:28:35 +01:00
// redirect to mfa factor if user has one, or redirect to set one up
checkMFAFactors(
session,
loginSettings,
authMethodResponse.authMethodTypes,
command.organization,
command.authRequestId,
);
// login user if no additional steps are required
if (command.authRequestId && session.id) {
const nextUrl = await getNextUrl(
{
sessionId: session.id,
authRequestId: command.authRequestId,
organization:
command.organization ?? session.factors?.user?.organizationId,
},
loginSettings?.defaultRedirectUri,
);
return { redirect: nextUrl };
}
const url = await getNextUrl(
{
loginName: session.factors.user.loginName,
organization: session.factors?.user?.organizationId,
},
loginSettings?.defaultRedirectUri,
);
return { redirect: url };
2024-12-17 15:57:42 +01:00
}
type resendVerifyEmailCommand = {
userId: string;
isInvite: boolean;
2024-12-19 11:28:35 +01:00
authRequestId?: string;
2024-12-17 15:57:42 +01:00
};
export async function resendVerification(command: resendVerifyEmailCommand) {
2024-12-19 11:28:35 +01:00
const host = (await headers()).get("host");
2024-12-17 15:57:42 +01:00
return command.isInvite
? resendInviteCode(command.userId)
2024-12-19 11:28:35 +01:00
: resendEmailCode(command.userId, host, command.authRequestId);
2024-12-17 15:57:42 +01:00
}
2024-12-19 08:59:39 +01:00
export type SendVerificationRedirectWithoutCheckCommand = {
organization?: string;
authRequestId?: string;
} & (
| { userId: string; loginName?: never }
| { userId?: never; loginName: string }
);
2024-12-17 15:57:42 +01:00
export async function sendVerificationRedirectWithoutCheck(
command: SendVerificationRedirectWithoutCheckCommand,
) {
if (!("loginName" in command || "userId" in command)) {
return { error: "No userId, nor loginname provided" };
}
2024-12-19 08:59:39 +01:00
let session: Session | undefined;
let user: User | undefined;
2024-12-17 15:57:42 +01:00
if ("loginName" in command) {
2024-12-19 08:59:39 +01:00
const sessionCookie = await getSessionCookieByLoginName({
2024-12-17 15:57:42 +01:00
loginName: command.loginName,
organization: command.organization,
}).catch((error) => {
console.warn("Ignored error:", error);
});
2024-12-19 08:59:39 +01:00
if (!sessionCookie) {
return { error: "Could not load session cookie" };
2024-12-17 15:57:42 +01:00
}
2024-12-19 08:59:39 +01:00
session = await getSession({
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
}).then((response) => {
if (response?.session) {
return response.session;
}
});
2024-12-17 15:57:42 +01:00
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };
}
const userResponse = await getUserByID(session?.factors?.user?.id);
2024-12-19 08:59:39 +01:00
if (!userResponse?.user) {
return { error: "Could not load user" };
2024-12-17 15:57:42 +01:00
}
user = userResponse.user;
2024-12-19 08:59:39 +01:00
} else if ("userId" in command) {
const userResponse = await getUserByID(command.userId);
2024-12-17 15:57:42 +01:00
2024-12-19 08:59:39 +01:00
if (!userResponse?.user) {
return { error: "Could not load user" };
}
user = userResponse.user;
const checks = create(ChecksSchema, {
user: {
search: {
case: "loginName",
value: userResponse.user.preferredLoginName,
},
},
});
session = await createSessionAndUpdateCookie(
checks,
undefined,
command.authRequestId,
2024-12-17 15:57:42 +01:00
);
}
2024-12-19 08:59:39 +01:00
if (!session?.factors?.user?.id) {
2024-12-17 15:57:42 +01:00
return { error: "Could not create session for user" };
}
2024-12-19 08:59:39 +01:00
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };
}
if (!user) {
return { error: "Could not load user" };
}
const authMethodResponse = await listAuthenticationMethodTypes(user.userId);
2024-12-17 15:57:42 +01:00
if (!authMethodResponse || !authMethodResponse.authMethodTypes) {
return { error: "Could not load possible authenticators" };
}
2024-12-19 08:59:39 +01:00
2024-12-17 15:57:42 +01:00
// if no authmethods are found on the user, redirect to set one up
if (
authMethodResponse &&
authMethodResponse.authMethodTypes &&
authMethodResponse.authMethodTypes.length == 0
) {
const params = new URLSearchParams({
sessionId: session.id,
});
if (session.factors?.user?.loginName) {
params.set("loginName", session.factors?.user?.loginName);
}
return { redirect: `/authenticator/set?${params}` };
}
2024-12-19 08:59:39 +01:00
2024-12-19 11:28:35 +01:00
const loginSettings = await getLoginSettings(user.details?.resourceOwner);
2024-12-19 08:59:39 +01:00
// redirect to mfa factor if user has one, or redirect to set one up
checkMFAFactors(
session,
loginSettings,
authMethodResponse.authMethodTypes,
command.organization,
command.authRequestId,
);
// login user if no additional steps are required
if (command.authRequestId && session.id) {
const nextUrl = await getNextUrl(
{
sessionId: session.id,
authRequestId: command.authRequestId,
organization:
command.organization ?? session.factors?.user?.organizationId,
},
loginSettings?.defaultRedirectUri,
);
return { redirect: nextUrl };
}
const url = await getNextUrl(
{
loginName: session.factors.user.loginName,
organization: session.factors?.user?.organizationId,
},
loginSettings?.defaultRedirectUri,
);
return { redirect: url };
2024-12-17 15:57:42 +01:00
}