mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-10 16:15:12 +00:00
# Which Problems Are Solved Invalid formatting in the login codebase due to invalid prettier configuration # How the Problems Are Solved Fxed .prettierignore wildcards for login source files # Additional Changes Removed .prettierrc file because there is already a prettier.config.mjs file # Additional Context
293 lines
9.2 KiB
TypeScript
293 lines
9.2 KiB
TypeScript
"use server";
|
|
|
|
import { createLogger } from "@/lib/logger";
|
|
import {
|
|
createInviteCode,
|
|
getLoginSettings,
|
|
getSession,
|
|
getUserByID,
|
|
listAuthenticationMethodTypes,
|
|
verifyEmail,
|
|
verifyInviteCode,
|
|
verifyTOTPRegistration,
|
|
sendEmailCode as zitadelSendEmailCode,
|
|
} from "@/lib/zitadel";
|
|
import crypto from "crypto";
|
|
|
|
import { create } from "@zitadel/client";
|
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
|
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
|
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
|
import { getTranslations } from "next-intl/server";
|
|
import { cookies, headers } from "next/headers";
|
|
import { completeFlowOrGetUrl } from "../client";
|
|
import { getSessionCookieByLoginName } from "../cookies";
|
|
import { getOrSetFingerprintId } from "../fingerprint";
|
|
import { getServiceConfig } from "../service-url";
|
|
import { loadMostRecentSession } from "../session";
|
|
import { checkMFAFactors } from "../verify-helper";
|
|
import { createSessionAndUpdateCookie } from "./cookie";
|
|
import { getPublicHostWithProtocol } from "./host";
|
|
|
|
const logger = createLogger("verify");
|
|
|
|
export async function verifyTOTP(code: string, loginName?: string, organization?: string) {
|
|
const _headers = await headers();
|
|
const { serviceConfig } = getServiceConfig(_headers);
|
|
|
|
return loadMostRecentSession({
|
|
serviceConfig,
|
|
sessionParams: {
|
|
loginName,
|
|
organization,
|
|
},
|
|
}).then((session) => {
|
|
if (session?.factors?.user?.id) {
|
|
return verifyTOTPRegistration({ serviceConfig, code, userId: session.factors.user.id });
|
|
} else {
|
|
throw Error("No user id found in session.");
|
|
}
|
|
});
|
|
}
|
|
|
|
type VerifyUserByEmailCommand = {
|
|
userId: string;
|
|
loginName?: string; // to determine already existing session
|
|
organization?: string;
|
|
code: string;
|
|
isInvite: boolean;
|
|
requestId?: string;
|
|
};
|
|
|
|
export async function sendVerification(command: VerifyUserByEmailCommand) {
|
|
const t = await getTranslations("verify");
|
|
const _headers = await headers();
|
|
const { serviceConfig } = getServiceConfig(_headers);
|
|
|
|
const verifyResponse = command.isInvite
|
|
? await verifyInviteCode({ serviceConfig, userId: command.userId, verificationCode: command.code }).catch((error) => {
|
|
logger.warn("Could not verify invite:", { error });
|
|
return { error: t("errors.couldNotVerifyInvite") };
|
|
})
|
|
: await verifyEmail({ serviceConfig, userId: command.userId, verificationCode: command.code }).catch((error) => {
|
|
logger.warn("Could not verify email:", { error });
|
|
return { error: t("errors.couldNotVerifyEmail") };
|
|
});
|
|
|
|
if ("error" in verifyResponse) {
|
|
return verifyResponse;
|
|
}
|
|
|
|
if (!verifyResponse) {
|
|
return { error: t("errors.couldNotVerify") };
|
|
}
|
|
|
|
let session: Session | undefined;
|
|
const userResponse = await getUserByID({ serviceConfig, userId: command.userId });
|
|
|
|
if (!userResponse || !userResponse.user) {
|
|
return { error: t("errors.couldNotLoadUser") };
|
|
}
|
|
|
|
const user = userResponse.user;
|
|
|
|
const sessionCookie = await getSessionCookieByLoginName({
|
|
loginName: "loginName" in command ? command.loginName : user.preferredLoginName,
|
|
organization: command.organization,
|
|
});
|
|
|
|
if (sessionCookie) {
|
|
session = await getSession({ serviceConfig, sessionId: sessionCookie.id, sessionToken: sessionCookie.token })
|
|
.then((response) => {
|
|
if (response?.session) {
|
|
return response.session;
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
// user session is not found, so we create a new one
|
|
logger.warn("[verify] user session is not found, so we create a new one", { error });
|
|
return undefined;
|
|
});
|
|
}
|
|
|
|
// load auth methods for user
|
|
const authMethodResponse = await listAuthenticationMethodTypes({ serviceConfig, userId: user.userId });
|
|
|
|
if (!authMethodResponse || !authMethodResponse.authMethodTypes) {
|
|
return { error: t("errors.couldNotLoadAuthenticators") };
|
|
}
|
|
|
|
const hasPrimaryMethod =
|
|
authMethodResponse?.authMethodTypes?.some(
|
|
(m: AuthenticationMethodType) =>
|
|
m === AuthenticationMethodType.PASSWORD ||
|
|
m === AuthenticationMethodType.PASSKEY ||
|
|
m === AuthenticationMethodType.IDP,
|
|
) ?? false;
|
|
|
|
// if no primary auth methods are found on the user, redirect to set one up
|
|
if (!hasPrimaryMethod) {
|
|
if (!session) {
|
|
const checks = create(ChecksSchema, {
|
|
user: {
|
|
search: {
|
|
case: "loginName",
|
|
value: userResponse.user.preferredLoginName,
|
|
},
|
|
},
|
|
});
|
|
|
|
const result = await createSessionAndUpdateCookie({
|
|
checks,
|
|
requestId: command.requestId,
|
|
});
|
|
session = result.session;
|
|
}
|
|
|
|
if (!session) {
|
|
return { error: t("errors.couldNotCreateSession") };
|
|
}
|
|
|
|
const params = new URLSearchParams({
|
|
sessionId: session.id,
|
|
});
|
|
|
|
if (session.factors?.user?.loginName) {
|
|
params.set("loginName", session.factors?.user?.loginName);
|
|
}
|
|
|
|
// set hash of userId and userAgentId to prevent attacks, checks are done for users with invalid sessions and invalid userAgentId
|
|
const cookiesList = await cookies();
|
|
const userAgentId = await getOrSetFingerprintId();
|
|
|
|
const verificationCheck = crypto.createHash("sha256").update(`${user.userId}:${userAgentId}`).digest("hex");
|
|
|
|
await cookiesList.set({
|
|
name: "verificationCheck",
|
|
value: verificationCheck,
|
|
httpOnly: true,
|
|
path: "/",
|
|
maxAge: 300, // 5 minutes
|
|
});
|
|
|
|
return { redirect: `/authenticator/set?${params}` };
|
|
}
|
|
|
|
// if no session found only show success page,
|
|
// if user is invited, recreate invite flow to not depend on session
|
|
if (!session?.factors?.user?.id) {
|
|
const verifySuccessParams = new URLSearchParams({});
|
|
|
|
if (command.userId) {
|
|
verifySuccessParams.set("userId", command.userId);
|
|
}
|
|
|
|
if (("loginName" in command && command.loginName) || user.preferredLoginName) {
|
|
verifySuccessParams.set(
|
|
"loginName",
|
|
"loginName" in command && command.loginName ? command.loginName : user.preferredLoginName,
|
|
);
|
|
}
|
|
if (command.requestId) {
|
|
verifySuccessParams.set("requestId", command.requestId);
|
|
}
|
|
if (command.organization) {
|
|
verifySuccessParams.set("organization", command.organization);
|
|
}
|
|
|
|
return { redirect: `/verify/success?${verifySuccessParams}` };
|
|
}
|
|
|
|
const loginSettings = await getLoginSettings({ serviceConfig, organization: user.details?.resourceOwner });
|
|
|
|
// redirect to mfa factor if user has one, or redirect to set one up
|
|
const mfaFactorCheck = await checkMFAFactors(
|
|
serviceConfig,
|
|
session,
|
|
loginSettings,
|
|
authMethodResponse.authMethodTypes,
|
|
command.organization,
|
|
command.requestId,
|
|
);
|
|
|
|
if (mfaFactorCheck?.redirect) {
|
|
return mfaFactorCheck;
|
|
}
|
|
|
|
// login user if no additional steps are required
|
|
if (command.requestId && session.id) {
|
|
return completeFlowOrGetUrl(
|
|
{
|
|
sessionId: session.id,
|
|
requestId: command.requestId,
|
|
organization: command.organization ?? session.factors?.user?.organizationId,
|
|
},
|
|
loginSettings?.defaultRedirectUri,
|
|
);
|
|
}
|
|
|
|
// Regular flow - return URL for client-side navigation
|
|
return completeFlowOrGetUrl(
|
|
{
|
|
loginName: session.factors.user.loginName,
|
|
organization: session.factors?.user?.organizationId,
|
|
},
|
|
loginSettings?.defaultRedirectUri,
|
|
);
|
|
}
|
|
|
|
type resendVerifyEmailCommand = {
|
|
userId: string;
|
|
isInvite: boolean;
|
|
requestId?: string;
|
|
};
|
|
|
|
export async function resendVerification(command: resendVerifyEmailCommand) {
|
|
const t = await getTranslations("verify");
|
|
const _headers = await headers();
|
|
const { serviceConfig } = getServiceConfig(_headers);
|
|
const hostWithProtocol = await getPublicHostWithProtocol(_headers);
|
|
|
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
|
|
|
|
return command.isInvite
|
|
? createInviteCode({
|
|
serviceConfig,
|
|
userId: command.userId,
|
|
urlTemplate:
|
|
`${hostWithProtocol}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` +
|
|
(command.requestId ? `&requestId=${command.requestId}` : ""),
|
|
}).catch((error) => {
|
|
if (error.code === 9) {
|
|
return { error: t("errors.userAlreadyVerified") };
|
|
}
|
|
return { error: t("errors.couldNotResendInvite") };
|
|
})
|
|
: zitadelSendEmailCode({
|
|
serviceConfig,
|
|
userId: command.userId,
|
|
urlTemplate:
|
|
`${hostWithProtocol}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` +
|
|
(command.requestId ? `&requestId=${command.requestId}` : ""),
|
|
});
|
|
}
|
|
|
|
type SendEmailCommand = {
|
|
userId: string;
|
|
urlTemplate: string;
|
|
};
|
|
|
|
export async function sendEmailCode(command: SendEmailCommand) {
|
|
const _headers = await headers();
|
|
const { serviceConfig } = getServiceConfig(_headers);
|
|
|
|
return zitadelSendEmailCode({ serviceConfig, userId: command.userId, urlTemplate: command.urlTemplate });
|
|
}
|
|
|
|
export async function sendInviteEmailCode(command: SendEmailCommand) {
|
|
const _headers = await headers();
|
|
const { serviceConfig } = getServiceConfig(_headers);
|
|
|
|
return createInviteCode({ serviceConfig, userId: command.userId, urlTemplate: command.urlTemplate });
|
|
}
|