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
|
|
|
}
|