mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 13:27:34 +00:00
Merge pull request #369 from zitadel/qa
Promote qa to prod: SAML, MFA init prompt
This commit is contained in:
@@ -16,6 +16,26 @@ FirstInstance:
|
|||||||
ExpirationDate: 2099-01-01T00:00:00Z
|
ExpirationDate: 2099-01-01T00:00:00Z
|
||||||
|
|
||||||
DefaultInstance:
|
DefaultInstance:
|
||||||
|
LoginPolicy:
|
||||||
|
AllowUsernamePassword: true # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_ALLOWUSERNAMEPASSWORD
|
||||||
|
AllowRegister: true # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_ALLOWREGISTER
|
||||||
|
AllowExternalIDP: true # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_ALLOWEXTERNALIDP
|
||||||
|
ForceMFA: false # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_FORCEMFA
|
||||||
|
HidePasswordReset: false # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_HIDEPASSWORDRESET
|
||||||
|
IgnoreUnknownUsernames: false # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_IGNOREUNKNOWNUSERNAMES
|
||||||
|
AllowDomainDiscovery: true # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_ALLOWDOMAINDISCOVERY
|
||||||
|
# 1 is allowed, 0 is not allowed
|
||||||
|
PasswordlessType: 1 # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_PASSWORDLESSTYPE
|
||||||
|
# DefaultRedirectURL is empty by default because we use the Console UI
|
||||||
|
DefaultRedirectURI: # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_DEFAULTREDIRECTURI
|
||||||
|
# 240h = 10d
|
||||||
|
PasswordCheckLifetime: 240h # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_PASSWORDCHECKLIFETIME
|
||||||
|
# 240h = 10d
|
||||||
|
ExternalLoginCheckLifetime: 240h # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_EXTERNALLOGINCHECKLIFETIME
|
||||||
|
# 720h = 30d
|
||||||
|
MfaInitSkipLifetime: 0h # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_MFAINITSKIPLIFETIME
|
||||||
|
SecondFactorCheckLifetime: 18h # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_SECONDFACTORCHECKLIFETIME
|
||||||
|
MultiFactorCheckLifetime: 12h # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_MULTIFACTORCHECKLIFETIME
|
||||||
PrivacyPolicy:
|
PrivacyPolicy:
|
||||||
TOSLink: "https://zitadel.com/docs/legal/terms-of-service"
|
TOSLink: "https://zitadel.com/docs/legal/terms-of-service"
|
||||||
PrivacyLink: "https://zitadel.com/docs/legal/policies/privacy-policy"
|
PrivacyLink: "https://zitadel.com/docs/legal/policies/privacy-policy"
|
||||||
|
@@ -71,7 +71,8 @@
|
|||||||
},
|
},
|
||||||
"set": {
|
"set": {
|
||||||
"title": "2-Faktor einrichten",
|
"title": "2-Faktor einrichten",
|
||||||
"description": "Wählen Sie einen der folgenden zweiten Faktoren."
|
"description": "Wählen Sie einen der folgenden zweiten Faktoren.",
|
||||||
|
"skip": "Überspringen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"otp": {
|
"otp": {
|
||||||
|
@@ -71,7 +71,8 @@
|
|||||||
},
|
},
|
||||||
"set": {
|
"set": {
|
||||||
"title": "Set up 2-Factor",
|
"title": "Set up 2-Factor",
|
||||||
"description": "Choose one of the following second factors."
|
"description": "Choose one of the following second factors.",
|
||||||
|
"skip": "Skip"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"otp": {
|
"otp": {
|
||||||
|
@@ -71,7 +71,8 @@
|
|||||||
},
|
},
|
||||||
"set": {
|
"set": {
|
||||||
"title": "Configurar autenticación de 2 factores",
|
"title": "Configurar autenticación de 2 factores",
|
||||||
"description": "Elige uno de los siguientes factores secundarios."
|
"description": "Elige uno de los siguientes factores secundarios.",
|
||||||
|
"skip": "Omitir"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"otp": {
|
"otp": {
|
||||||
|
@@ -71,7 +71,8 @@
|
|||||||
},
|
},
|
||||||
"set": {
|
"set": {
|
||||||
"title": "Configura l'autenticazione a 2 fattori",
|
"title": "Configura l'autenticazione a 2 fattori",
|
||||||
"description": "Scegli uno dei seguenti secondi fattori."
|
"description": "Scegli uno dei seguenti secondi fattori.",
|
||||||
|
"skip": "Salta"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"otp": {
|
"otp": {
|
||||||
|
@@ -71,7 +71,8 @@
|
|||||||
},
|
},
|
||||||
"set": {
|
"set": {
|
||||||
"title": "设置双因素认证",
|
"title": "设置双因素认证",
|
||||||
"description": "选择以下的一个第二因素。"
|
"description": "选择以下的一个第二因素。",
|
||||||
|
"skip": "跳过"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"otp": {
|
"otp": {
|
||||||
|
@@ -389,7 +389,6 @@ In future, self service options to jump to are shown below, like:
|
|||||||
|
|
||||||
## Currently NOT Supported
|
## Currently NOT Supported
|
||||||
|
|
||||||
- Login Settings: multifactor init prompt
|
|
||||||
- forceMFA on login settings is not checked for IDPs
|
- forceMFA on login settings is not checked for IDPs
|
||||||
|
|
||||||
Also note that IDP logins are considered as valid MFA. An additional MFA check will be implemented in future if enforced.
|
Also note that IDP logins are considered as valid MFA. An additional MFA check will be implemented in future if enforced.
|
||||||
|
@@ -36,7 +36,7 @@ export default async function Page(props: {
|
|||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
const t = await getTranslations({ locale, namespace: "accounts" });
|
const t = await getTranslations({ locale, namespace: "accounts" });
|
||||||
|
|
||||||
const authRequestId = searchParams?.authRequestId;
|
const requestId = searchParams?.requestId;
|
||||||
const organization = searchParams?.organization;
|
const organization = searchParams?.organization;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
@@ -62,8 +62,8 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.append("authRequestId", authRequestId);
|
params.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization) {
|
if (organization) {
|
||||||
@@ -77,7 +77,7 @@ export default async function Page(props: {
|
|||||||
<p className="ztdl-p mb-6 block">{t("description")}</p>
|
<p className="ztdl-p mb-6 block">{t("description")}</p>
|
||||||
|
|
||||||
<div className="flex flex-col w-full space-y-2">
|
<div className="flex flex-col w-full space-y-2">
|
||||||
<SessionsList sessions={sessions} authRequestId={authRequestId} />
|
<SessionsList sessions={sessions} requestId={requestId} />
|
||||||
<Link href={`/loginname?` + params}>
|
<Link href={`/loginname?` + params}>
|
||||||
<div className="flex flex-row items-center py-3 px-4 hover:bg-black/10 dark:hover:bg-white/10 rounded-md transition-all">
|
<div className="flex flex-row items-center py-3 px-4 hover:bg-black/10 dark:hover:bg-white/10 rounded-md transition-all">
|
||||||
<div className="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
|
<div className="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
|
||||||
|
@@ -27,7 +27,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "authenticator" });
|
const t = await getTranslations({ locale, namespace: "authenticator" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const { loginName, authRequestId, organization, sessionId } = searchParams;
|
const { loginName, requestId, organization, sessionId } = searchParams;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
@@ -141,8 +141,8 @@ export default async function Page(props: {
|
|||||||
params.set("organization", sessionWithData.factors?.user?.organizationId);
|
params.set("organization", sessionWithData.factors?.user?.organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.set("authRequestId", authRequestId);
|
params.set("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -174,7 +174,7 @@ export default async function Page(props: {
|
|||||||
{loginSettings?.allowExternalIdp && identityProviders && (
|
{loginSettings?.allowExternalIdp && identityProviders && (
|
||||||
<SignInWithIdp
|
<SignInWithIdp
|
||||||
identityProviders={identityProviders}
|
identityProviders={identityProviders}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={sessionWithData.factors?.user?.organizationId}
|
organization={sessionWithData.factors?.user?.organizationId}
|
||||||
linkOnly={true} // tell the callback function to just link the IDP and not login, to get an error when user is already available
|
linkOnly={true} // tell the callback function to just link the IDP and not login, to get an error when user is already available
|
||||||
></SignInWithIdp>
|
></SignInWithIdp>
|
||||||
|
@@ -36,7 +36,7 @@ export default async function Page(props: {
|
|||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
const t = await getTranslations({ locale, namespace: "idp" });
|
const t = await getTranslations({ locale, namespace: "idp" });
|
||||||
const { id, token, authRequestId, organization, link } = searchParams;
|
const { id, token, requestId, organization, link } = searchParams;
|
||||||
const { provider } = params;
|
const { provider } = params;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
@@ -68,7 +68,7 @@ export default async function Page(props: {
|
|||||||
return loginSuccess(
|
return loginSuccess(
|
||||||
userId,
|
userId,
|
||||||
{ idpIntentId: id, idpIntentToken: token },
|
{ idpIntentId: id, idpIntentToken: token },
|
||||||
authRequestId,
|
requestId,
|
||||||
branding,
|
branding,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ export default async function Page(props: {
|
|||||||
return linkingSuccess(
|
return linkingSuccess(
|
||||||
userId,
|
userId,
|
||||||
{ idpIntentId: id, idpIntentToken: token },
|
{ idpIntentId: id, idpIntentToken: token },
|
||||||
authRequestId,
|
requestId,
|
||||||
branding,
|
branding,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ export default async function Page(props: {
|
|||||||
return linkingSuccess(
|
return linkingSuccess(
|
||||||
foundUser.userId,
|
foundUser.userId,
|
||||||
{ idpIntentId: id, idpIntentToken: token },
|
{ idpIntentId: id, idpIntentToken: token },
|
||||||
authRequestId,
|
requestId,
|
||||||
branding,
|
branding,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ export default async function Page(props: {
|
|||||||
<IdpSignin
|
<IdpSignin
|
||||||
userId={newUser.userId}
|
userId={newUser.userId}
|
||||||
idpIntent={{ idpIntentId: id, idpIntentToken: token }}
|
idpIntent={{ idpIntentId: id, idpIntentToken: token }}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DynamicTheme>
|
</DynamicTheme>
|
||||||
|
@@ -12,7 +12,7 @@ export default async function Page(props: {
|
|||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
const t = await getTranslations({ locale, namespace: "idp" });
|
const t = await getTranslations({ locale, namespace: "idp" });
|
||||||
|
|
||||||
const authRequestId = searchParams?.authRequestId;
|
const requestId = searchParams?.requestId;
|
||||||
const organization = searchParams?.organization;
|
const organization = searchParams?.organization;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
@@ -41,7 +41,7 @@ export default async function Page(props: {
|
|||||||
{identityProviders && (
|
{identityProviders && (
|
||||||
<SignInWithIdp
|
<SignInWithIdp
|
||||||
identityProviders={identityProviders}
|
identityProviders={identityProviders}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
></SignInWithIdp>
|
></SignInWithIdp>
|
||||||
)}
|
)}
|
||||||
|
@@ -20,7 +20,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "loginname" });
|
const t = await getTranslations({ locale, namespace: "loginname" });
|
||||||
|
|
||||||
const loginName = searchParams?.loginName;
|
const loginName = searchParams?.loginName;
|
||||||
const authRequestId = searchParams?.authRequestId;
|
const requestId = searchParams?.requestId;
|
||||||
const organization = searchParams?.organization;
|
const organization = searchParams?.organization;
|
||||||
const suffix = searchParams?.suffix;
|
const suffix = searchParams?.suffix;
|
||||||
const submit: boolean = searchParams?.submit === "true";
|
const submit: boolean = searchParams?.submit === "true";
|
||||||
@@ -72,7 +72,7 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
<UsernameForm
|
<UsernameForm
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user
|
organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user
|
||||||
loginSettings={contextLoginSettings}
|
loginSettings={contextLoginSettings}
|
||||||
suffix={suffix}
|
suffix={suffix}
|
||||||
@@ -82,7 +82,7 @@ export default async function Page(props: {
|
|||||||
{identityProviders && (
|
{identityProviders && (
|
||||||
<SignInWithIdp
|
<SignInWithIdp
|
||||||
identityProviders={identityProviders}
|
identityProviders={identityProviders}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
></SignInWithIdp>
|
></SignInWithIdp>
|
||||||
)}
|
)}
|
||||||
|
@@ -22,7 +22,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "mfa" });
|
const t = await getTranslations({ locale, namespace: "mfa" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const { loginName, authRequestId, organization, sessionId } = searchParams;
|
const { loginName, requestId, organization, sessionId } = searchParams;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
@@ -114,7 +114,7 @@ export default async function Page(props: {
|
|||||||
<ChooseSecondFactor
|
<ChooseSecondFactor
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
userMethods={sessionFactors.authMethods ?? []}
|
userMethods={sessionFactors.authMethods ?? []}
|
||||||
></ChooseSecondFactor>
|
></ChooseSecondFactor>
|
||||||
|
@@ -42,23 +42,17 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "mfa" });
|
const t = await getTranslations({ locale, namespace: "mfa" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const {
|
const { loginName, checkAfter, force, requestId, organization, sessionId } =
|
||||||
loginName,
|
searchParams;
|
||||||
checkAfter,
|
|
||||||
force,
|
|
||||||
authRequestId,
|
|
||||||
organization,
|
|
||||||
sessionId,
|
|
||||||
} = searchParams;
|
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
const sessionWithData = sessionId
|
const sessionWithData = sessionId
|
||||||
? await loadSessionById(serviceUrl, sessionId, organization)
|
? await loadSessionById(sessionId, organization)
|
||||||
: await loadSessionByLoginname(serviceUrl, loginName, organization);
|
: await loadSessionByLoginname(loginName, organization);
|
||||||
|
|
||||||
async function getAuthMethodsAndUser(host: string, session?: Session) {
|
async function getAuthMethodsAndUser(session?: Session) {
|
||||||
const userId = session?.factors?.user?.id;
|
const userId = session?.factors?.user?.id;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
@@ -86,7 +80,6 @@ export default async function Page(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadSessionByLoginname(
|
async function loadSessionByLoginname(
|
||||||
host: string,
|
|
||||||
loginName?: string,
|
loginName?: string,
|
||||||
organization?: string,
|
organization?: string,
|
||||||
) {
|
) {
|
||||||
@@ -98,23 +91,18 @@ export default async function Page(props: {
|
|||||||
organization,
|
organization,
|
||||||
},
|
},
|
||||||
}).then((session) => {
|
}).then((session) => {
|
||||||
return getAuthMethodsAndUser(serviceUrl, session);
|
return getAuthMethodsAndUser(session);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSessionById(
|
async function loadSessionById(sessionId: string, organization?: string) {
|
||||||
host: string,
|
|
||||||
sessionId: string,
|
|
||||||
organization?: string,
|
|
||||||
) {
|
|
||||||
const recent = await getSessionCookieById({ sessionId, organization });
|
const recent = await getSessionCookieById({ sessionId, organization });
|
||||||
return getSession({
|
return getSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
sessionId: recent.id,
|
sessionId: recent.id,
|
||||||
sessionToken: recent.token,
|
sessionToken: recent.token,
|
||||||
}).then((sessionResponse) => {
|
}).then((sessionResponse) => {
|
||||||
return getAuthMethodsAndUser(serviceUrl, sessionResponse.session);
|
return getAuthMethodsAndUser(sessionResponse.session);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,17 +141,20 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
{isSessionValid(sessionWithData).valid &&
|
{isSessionValid(sessionWithData).valid &&
|
||||||
loginSettings &&
|
loginSettings &&
|
||||||
sessionWithData && (
|
sessionWithData &&
|
||||||
|
sessionWithData.factors?.user?.id && (
|
||||||
<ChooseSecondFactorToSetup
|
<ChooseSecondFactorToSetup
|
||||||
|
userId={sessionWithData.factors?.user?.id}
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
loginSettings={loginSettings}
|
loginSettings={loginSettings}
|
||||||
userMethods={sessionWithData.authMethods ?? []}
|
userMethods={sessionWithData.authMethods ?? []}
|
||||||
phoneVerified={sessionWithData.phoneVerified ?? false}
|
phoneVerified={sessionWithData.phoneVerified ?? false}
|
||||||
emailVerified={sessionWithData.emailVerified ?? false}
|
emailVerified={sessionWithData.emailVerified ?? false}
|
||||||
checkAfter={checkAfter === "true"}
|
checkAfter={checkAfter === "true"}
|
||||||
|
force={force === "true"}
|
||||||
></ChooseSecondFactorToSetup>
|
></ChooseSecondFactorToSetup>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@@ -34,7 +34,7 @@ export default async function Page(props: {
|
|||||||
const {
|
const {
|
||||||
loginName, // send from password page
|
loginName, // send from password page
|
||||||
userId, // send from email link
|
userId, // send from email link
|
||||||
authRequestId,
|
requestId,
|
||||||
sessionId,
|
sessionId,
|
||||||
organization,
|
organization,
|
||||||
code,
|
code,
|
||||||
@@ -115,7 +115,7 @@ export default async function Page(props: {
|
|||||||
<LoginOTP
|
<LoginOTP
|
||||||
loginName={loginName ?? session.factors?.user?.loginName}
|
loginName={loginName ?? session.factors?.user?.loginName}
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={
|
organization={
|
||||||
organization ?? session?.factors?.user?.organizationId
|
organization ?? session?.factors?.user?.organizationId
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "otp" });
|
const t = await getTranslations({ locale, namespace: "otp" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const { loginName, organization, sessionId, authRequestId, checkAfter } =
|
const { loginName, organization, sessionId, requestId, checkAfter } =
|
||||||
searchParams;
|
searchParams;
|
||||||
const { method } = params;
|
const { method } = params;
|
||||||
|
|
||||||
@@ -111,22 +111,22 @@ export default async function Page(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (checkAfter) {
|
if (checkAfter) {
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
paramsToContinue.append("authRequestId", authRequestId);
|
paramsToContinue.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
urlToContinue = `/otp/${method}?` + paramsToContinue;
|
urlToContinue = `/otp/${method}?` + paramsToContinue;
|
||||||
// immediately check the OTP on the next page if sms or email was set up
|
// immediately check the OTP on the next page if sms or email was set up
|
||||||
if (["email", "sms"].includes(method)) {
|
if (["email", "sms"].includes(method)) {
|
||||||
return redirect(urlToContinue);
|
return redirect(urlToContinue);
|
||||||
}
|
}
|
||||||
} else if (authRequestId && sessionId) {
|
} else if (requestId && sessionId) {
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
paramsToContinue.append("authRequest", authRequestId);
|
paramsToContinue.append("authRequest", requestId);
|
||||||
}
|
}
|
||||||
urlToContinue = `/login?` + paramsToContinue;
|
urlToContinue = `/login?` + paramsToContinue;
|
||||||
} else if (loginName) {
|
} else if (loginName) {
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
paramsToContinue.append("authRequestId", authRequestId);
|
paramsToContinue.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
urlToContinue = `/signedin?` + paramsToContinue;
|
urlToContinue = `/signedin?` + paramsToContinue;
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,7 @@ export default async function Page(props: {
|
|||||||
secret={totpResponse.secret as string}
|
secret={totpResponse.secret as string}
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
checkAfter={checkAfter === "true"}
|
checkAfter={checkAfter === "true"}
|
||||||
loginSettings={loginSettings}
|
loginSettings={loginSettings}
|
||||||
|
@@ -17,7 +17,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "passkey" });
|
const t = await getTranslations({ locale, namespace: "passkey" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const { loginName, altPassword, authRequestId, organization, sessionId } =
|
const { loginName, altPassword, requestId, organization, sessionId } =
|
||||||
searchParams;
|
searchParams;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
@@ -76,7 +76,7 @@ export default async function Page(props: {
|
|||||||
<LoginPasskey
|
<LoginPasskey
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
altPassword={altPassword === "true"}
|
altPassword={altPassword === "true"}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
/>
|
/>
|
||||||
|
@@ -16,8 +16,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "passkey" });
|
const t = await getTranslations({ locale, namespace: "passkey" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const { loginName, prompt, organization, authRequestId, userId } =
|
const { loginName, prompt, organization, requestId, userId } = searchParams;
|
||||||
searchParams;
|
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
@@ -76,7 +75,7 @@ export default async function Page(props: {
|
|||||||
sessionId={session.id}
|
sessionId={session.id}
|
||||||
isPrompt={!!prompt}
|
isPrompt={!!prompt}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -23,7 +23,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "password" });
|
const t = await getTranslations({ locale, namespace: "password" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const { loginName, organization, authRequestId } = searchParams;
|
const { loginName, organization, requestId } = searchParams;
|
||||||
|
|
||||||
// also allow no session to be found (ignoreUnkownUsername)
|
// also allow no session to be found (ignoreUnkownUsername)
|
||||||
const sessionFactors = await loadMostRecentSession({
|
const sessionFactors = await loadMostRecentSession({
|
||||||
@@ -84,7 +84,7 @@ export default async function Page(props: {
|
|||||||
<ChangePasswordForm
|
<ChangePasswordForm
|
||||||
sessionId={sessionFactors.id}
|
sessionId={sessionFactors.id}
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
passwordComplexitySettings={passwordComplexity}
|
passwordComplexitySettings={passwordComplexity}
|
||||||
/>
|
/>
|
||||||
|
@@ -22,7 +22,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "password" });
|
const t = await getTranslations({ locale, namespace: "password" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
let { loginName, organization, authRequestId, alt } = searchParams;
|
let { loginName, organization, requestId, alt } = searchParams;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
@@ -93,7 +93,7 @@ export default async function Page(props: {
|
|||||||
{loginName && (
|
{loginName && (
|
||||||
<PasswordForm
|
<PasswordForm
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user
|
organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user
|
||||||
loginSettings={loginSettings}
|
loginSettings={loginSettings}
|
||||||
promptPasswordless={
|
promptPasswordless={
|
||||||
|
@@ -23,7 +23,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "password" });
|
const t = await getTranslations({ locale, namespace: "password" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const { userId, loginName, organization, authRequestId, code, initial } =
|
const { userId, loginName, organization, requestId, code, initial } =
|
||||||
searchParams;
|
searchParams;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
@@ -113,7 +113,7 @@ export default async function Page(props: {
|
|||||||
code={code}
|
code={code}
|
||||||
userId={userId ?? (session?.factors?.user?.id as string)}
|
userId={userId ?? (session?.factors?.user?.id as string)}
|
||||||
loginName={loginName ?? (user?.preferredLoginName as string)}
|
loginName={loginName ?? (user?.preferredLoginName as string)}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
passwordComplexitySettings={passwordComplexity}
|
passwordComplexitySettings={passwordComplexity}
|
||||||
codeRequired={!(initial === "true")}
|
codeRequired={!(initial === "true")}
|
||||||
|
@@ -19,8 +19,7 @@ export default async function Page(props: {
|
|||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
const t = await getTranslations({ locale, namespace: "register" });
|
const t = await getTranslations({ locale, namespace: "register" });
|
||||||
|
|
||||||
let { firstname, lastname, email, organization, authRequestId } =
|
let { firstname, lastname, email, organization, requestId } = searchParams;
|
||||||
searchParams;
|
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
@@ -81,7 +80,7 @@ export default async function Page(props: {
|
|||||||
firstname={firstname}
|
firstname={firstname}
|
||||||
lastname={lastname}
|
lastname={lastname}
|
||||||
email={email}
|
email={email}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
loginSettings={loginSettings}
|
loginSettings={loginSettings}
|
||||||
></RegisterForm>
|
></RegisterForm>
|
||||||
)}
|
)}
|
||||||
|
@@ -19,8 +19,7 @@ export default async function Page(props: {
|
|||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
const t = await getTranslations({ locale, namespace: "register" });
|
const t = await getTranslations({ locale, namespace: "register" });
|
||||||
|
|
||||||
let { firstname, lastname, email, organization, authRequestId } =
|
let { firstname, lastname, email, organization, requestId } = searchParams;
|
||||||
searchParams;
|
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
@@ -79,7 +78,7 @@ export default async function Page(props: {
|
|||||||
firstname={firstname}
|
firstname={firstname}
|
||||||
lastname={lastname}
|
lastname={lastname}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
></SetRegisterPasswordForm>
|
></SetRegisterPasswordForm>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -6,6 +6,7 @@ import { getMostRecentCookieWithLoginname } from "@/lib/cookies";
|
|||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||||
import {
|
import {
|
||||||
createCallback,
|
createCallback,
|
||||||
|
createResponse,
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getLoginSettings,
|
getLoginSettings,
|
||||||
getSession,
|
getSession,
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
CreateCallbackRequestSchema,
|
CreateCallbackRequestSchema,
|
||||||
SessionSchema,
|
SessionSchema,
|
||||||
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||||
|
import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -24,16 +26,16 @@ async function loadSession(
|
|||||||
serviceUrl: string,
|
serviceUrl: string,
|
||||||
|
|
||||||
loginName: string,
|
loginName: string,
|
||||||
authRequestId?: string,
|
requestId?: string,
|
||||||
) {
|
) {
|
||||||
const recent = await getMostRecentCookieWithLoginname({ loginName });
|
const recent = await getMostRecentCookieWithLoginname({ loginName });
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId && requestId.startsWith("oidc_")) {
|
||||||
return createCallback({
|
return createCallback({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
req: create(CreateCallbackRequestSchema, {
|
req: create(CreateCallbackRequestSchema, {
|
||||||
authRequestId,
|
authRequestId: requestId,
|
||||||
callbackKind: {
|
callbackKind: {
|
||||||
case: "session",
|
case: "session",
|
||||||
value: create(SessionSchema, {
|
value: create(SessionSchema, {
|
||||||
@@ -45,7 +47,24 @@ async function loadSession(
|
|||||||
}).then(({ callbackUrl }) => {
|
}).then(({ callbackUrl }) => {
|
||||||
return redirect(callbackUrl);
|
return redirect(callbackUrl);
|
||||||
});
|
});
|
||||||
|
} else if (requestId && requestId.startsWith("saml_")) {
|
||||||
|
return createResponse({
|
||||||
|
serviceUrl,
|
||||||
|
req: create(CreateResponseRequestSchema, {
|
||||||
|
samlRequestId: requestId.replace("saml_", ""),
|
||||||
|
responseKind: {
|
||||||
|
case: "session",
|
||||||
|
value: {
|
||||||
|
sessionId: recent.id,
|
||||||
|
sessionToken: recent.token,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}).then(({ url }) => {
|
||||||
|
return redirect(url);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSession({
|
return getSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
@@ -66,12 +85,12 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
|||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
const { loginName, authRequestId, organization } = searchParams;
|
const { loginName, requestId, organization } = searchParams;
|
||||||
const sessionFactors = await loadSession(
|
const sessionFactors = await loadSession(
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
loginName,
|
loginName,
|
||||||
authRequestId,
|
requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const branding = await getBrandingSettings({
|
const branding = await getBrandingSettings({
|
||||||
@@ -81,7 +100,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let loginSettings;
|
let loginSettings;
|
||||||
if (!authRequestId) {
|
if (!requestId) {
|
||||||
loginSettings = await getLoginSettings({
|
loginSettings = await getLoginSettings({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "u2f" });
|
const t = await getTranslations({ locale, namespace: "u2f" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const { loginName, authRequestId, sessionId, organization } = searchParams;
|
const { loginName, requestId, sessionId, organization } = searchParams;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
@@ -80,7 +80,7 @@ export default async function Page(props: {
|
|||||||
<LoginPasskey
|
<LoginPasskey
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
altPassword={false}
|
altPassword={false}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
login={false} // this sets the userVerificationRequirement to discouraged as its used as second factor
|
login={false} // this sets the userVerificationRequirement to discouraged as its used as second factor
|
||||||
|
@@ -16,7 +16,7 @@ export default async function Page(props: {
|
|||||||
const t = await getTranslations({ locale, namespace: "u2f" });
|
const t = await getTranslations({ locale, namespace: "u2f" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const { loginName, organization, authRequestId, checkAfter } = searchParams;
|
const { loginName, organization, requestId, checkAfter } = searchParams;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
@@ -62,7 +62,7 @@ export default async function Page(props: {
|
|||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
sessionId={sessionFactors.id}
|
sessionId={sessionFactors.id}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
checkAfter={checkAfter === "true"}
|
checkAfter={checkAfter === "true"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@@ -22,7 +22,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
|||||||
const t = await getTranslations({ locale, namespace: "verify" });
|
const t = await getTranslations({ locale, namespace: "verify" });
|
||||||
const tError = await getTranslations({ locale, namespace: "error" });
|
const tError = await getTranslations({ locale, namespace: "error" });
|
||||||
|
|
||||||
const { userId, loginName, code, organization, authRequestId, invite } =
|
const { userId, loginName, code, organization, requestId, invite } =
|
||||||
searchParams;
|
searchParams;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
@@ -64,7 +64,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
|||||||
userId: sessionFactors?.factors?.user?.id,
|
userId: sessionFactors?.factors?.user?.id,
|
||||||
urlTemplate:
|
urlTemplate:
|
||||||
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` +
|
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` +
|
||||||
(authRequestId ? `&authRequestId=${authRequestId}` : ""),
|
(requestId ? `&requestId=${requestId}` : ""),
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error("Could not resend verification email", error);
|
console.error("Could not resend verification email", error);
|
||||||
throw Error("Failed to send verification email");
|
throw Error("Failed to send verification email");
|
||||||
@@ -77,7 +77,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
|||||||
userId,
|
userId,
|
||||||
urlTemplate:
|
urlTemplate:
|
||||||
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` +
|
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` +
|
||||||
(authRequestId ? `&authRequestId=${authRequestId}` : ""),
|
(requestId ? `&requestId=${requestId}` : ""),
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error("Could not resend verification email", error);
|
console.error("Could not resend verification email", error);
|
||||||
throw Error("Failed to send verification email");
|
throw Error("Failed to send verification email");
|
||||||
@@ -120,8 +120,8 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
|||||||
params.set("organization", organization);
|
params.set("organization", organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.set("authRequestId", authRequestId);
|
params.set("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -165,7 +165,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
|||||||
userId={id}
|
userId={id}
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
authMethods={authMethods}
|
authMethods={authMethods}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -176,7 +176,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
|||||||
userId={id}
|
userId={id}
|
||||||
code={code}
|
code={code}
|
||||||
isInvite={invite === "true"}
|
isInvite={invite === "true"}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,28 +1,28 @@
|
|||||||
import { getAllSessions } from "@/lib/cookies";
|
import { getAllSessions } from "@/lib/cookies";
|
||||||
import { idpTypeToSlug } from "@/lib/idp";
|
import { idpTypeToSlug } from "@/lib/idp";
|
||||||
|
import { loginWithOIDCandSession } from "@/lib/oidc";
|
||||||
|
import { loginWithSAMLandSession } from "@/lib/saml";
|
||||||
import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname";
|
import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||||
|
import { findValidSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
createCallback,
|
createCallback,
|
||||||
|
createResponse,
|
||||||
getActiveIdentityProviders,
|
getActiveIdentityProviders,
|
||||||
getAuthRequest,
|
getAuthRequest,
|
||||||
getLoginSettings,
|
|
||||||
getOrgsByDomain,
|
getOrgsByDomain,
|
||||||
listAuthenticationMethodTypes,
|
getSAMLRequest,
|
||||||
listSessions,
|
listSessions,
|
||||||
startIdentityProviderFlow,
|
startIdentityProviderFlow,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { create, timestampDate } from "@zitadel/client";
|
import { create } from "@zitadel/client";
|
||||||
import {
|
import { Prompt } from "@zitadel/proto/zitadel/oidc/v2/authorization_pb";
|
||||||
AuthRequest,
|
|
||||||
Prompt,
|
|
||||||
} from "@zitadel/proto/zitadel/oidc/v2/authorization_pb";
|
|
||||||
import {
|
import {
|
||||||
CreateCallbackRequestSchema,
|
CreateCallbackRequestSchema,
|
||||||
SessionSchema,
|
SessionSchema,
|
||||||
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||||
|
import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb";
|
||||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
@@ -30,12 +30,32 @@ export const dynamic = "force-dynamic";
|
|||||||
export const revalidate = false;
|
export const revalidate = false;
|
||||||
export const fetchCache = "default-no-store";
|
export const fetchCache = "default-no-store";
|
||||||
|
|
||||||
|
const gotoAccounts = ({
|
||||||
|
request,
|
||||||
|
requestId,
|
||||||
|
organization,
|
||||||
|
}: {
|
||||||
|
request: NextRequest;
|
||||||
|
requestId: string;
|
||||||
|
organization?: string;
|
||||||
|
}): NextResponse<unknown> => {
|
||||||
|
const accountsUrl = new URL("/accounts", request.url);
|
||||||
|
|
||||||
|
if (requestId) {
|
||||||
|
accountsUrl.searchParams.set("requestId", requestId);
|
||||||
|
}
|
||||||
|
if (organization) {
|
||||||
|
accountsUrl.searchParams.set("organization", organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.redirect(accountsUrl);
|
||||||
|
};
|
||||||
|
|
||||||
async function loadSessions({
|
async function loadSessions({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
ids,
|
ids,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
ids: string[];
|
ids: string[];
|
||||||
}): Promise<Session[]> {
|
}): Promise<Session[]> {
|
||||||
const response = await listSessions({
|
const response = await listSessions({
|
||||||
@@ -50,175 +70,23 @@ const ORG_SCOPE_REGEX = /urn:zitadel:iam:org:id:([0-9]+)/;
|
|||||||
const ORG_DOMAIN_SCOPE_REGEX = /urn:zitadel:iam:org:domain:primary:(.+)/; // TODO: check regex for all domain character options
|
const ORG_DOMAIN_SCOPE_REGEX = /urn:zitadel:iam:org:domain:primary:(.+)/; // TODO: check regex for all domain character options
|
||||||
const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/;
|
const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/;
|
||||||
|
|
||||||
/**
|
|
||||||
* mfa is required, session is not valid anymore (e.g. session expired, user logged out, etc.)
|
|
||||||
* to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId);
|
|
||||||
**/
|
|
||||||
async function isSessionValid(
|
|
||||||
serviceUrl: string,
|
|
||||||
|
|
||||||
session: Session,
|
|
||||||
): Promise<boolean> {
|
|
||||||
// session can't be checked without user
|
|
||||||
if (!session.factors?.user) {
|
|
||||||
console.warn("Session has no user");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mfaValid = true;
|
|
||||||
|
|
||||||
const authMethodTypes = await listAuthenticationMethodTypes({
|
|
||||||
serviceUrl,
|
|
||||||
|
|
||||||
userId: session.factors.user.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const authMethods = authMethodTypes.authMethodTypes;
|
|
||||||
if (authMethods && authMethods.includes(AuthenticationMethodType.TOTP)) {
|
|
||||||
mfaValid = !!session.factors.totp?.verifiedAt;
|
|
||||||
if (!mfaValid) {
|
|
||||||
console.warn(
|
|
||||||
"Session has no valid totpEmail factor",
|
|
||||||
session.factors.totp?.verifiedAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
authMethods &&
|
|
||||||
authMethods.includes(AuthenticationMethodType.OTP_EMAIL)
|
|
||||||
) {
|
|
||||||
mfaValid = !!session.factors.otpEmail?.verifiedAt;
|
|
||||||
if (!mfaValid) {
|
|
||||||
console.warn(
|
|
||||||
"Session has no valid otpEmail factor",
|
|
||||||
session.factors.otpEmail?.verifiedAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
authMethods &&
|
|
||||||
authMethods.includes(AuthenticationMethodType.OTP_SMS)
|
|
||||||
) {
|
|
||||||
mfaValid = !!session.factors.otpSms?.verifiedAt;
|
|
||||||
if (!mfaValid) {
|
|
||||||
console.warn(
|
|
||||||
"Session has no valid otpSms factor",
|
|
||||||
session.factors.otpSms?.verifiedAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
authMethods &&
|
|
||||||
authMethods.includes(AuthenticationMethodType.U2F)
|
|
||||||
) {
|
|
||||||
mfaValid = !!session.factors.webAuthN?.verifiedAt;
|
|
||||||
if (!mfaValid) {
|
|
||||||
console.warn(
|
|
||||||
"Session has no valid u2f factor",
|
|
||||||
session.factors.webAuthN?.verifiedAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// only check settings if no auth methods are available, as this would require a setup
|
|
||||||
const loginSettings = await getLoginSettings({
|
|
||||||
serviceUrl,
|
|
||||||
|
|
||||||
organization: session.factors?.user?.organizationId,
|
|
||||||
});
|
|
||||||
if (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) {
|
|
||||||
const otpEmail = session.factors.otpEmail?.verifiedAt;
|
|
||||||
const otpSms = session.factors.otpSms?.verifiedAt;
|
|
||||||
const totp = session.factors.totp?.verifiedAt;
|
|
||||||
const webAuthN = session.factors.webAuthN?.verifiedAt;
|
|
||||||
const idp = session.factors.intent?.verifiedAt; // TODO: forceMFA should not consider this as valid factor
|
|
||||||
|
|
||||||
// must have one single check
|
|
||||||
mfaValid = !!(otpEmail || otpSms || totp || webAuthN || idp);
|
|
||||||
if (!mfaValid) {
|
|
||||||
console.warn("Session has no valid multifactor", session.factors);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mfaValid = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const validPassword = session?.factors?.password?.verifiedAt;
|
|
||||||
const validPasskey = session?.factors?.webAuthN?.verifiedAt;
|
|
||||||
const validIDP = session?.factors?.intent?.verifiedAt;
|
|
||||||
|
|
||||||
const stillValid = session.expirationDate
|
|
||||||
? timestampDate(session.expirationDate).getTime() > new Date().getTime()
|
|
||||||
: true;
|
|
||||||
|
|
||||||
if (!stillValid) {
|
|
||||||
console.warn(
|
|
||||||
"Session is expired",
|
|
||||||
session.expirationDate
|
|
||||||
? timestampDate(session.expirationDate).toDateString()
|
|
||||||
: "no expiration date",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const validChecks = !!(validPassword || validPasskey || validIDP);
|
|
||||||
|
|
||||||
return stillValid && validChecks && mfaValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findValidSession(
|
|
||||||
serviceUrl: string,
|
|
||||||
|
|
||||||
sessions: Session[],
|
|
||||||
authRequest: AuthRequest,
|
|
||||||
): Promise<Session | undefined> {
|
|
||||||
const sessionsWithHint = sessions.filter((s) => {
|
|
||||||
if (authRequest.hintUserId) {
|
|
||||||
return s.factors?.user?.id === authRequest.hintUserId;
|
|
||||||
}
|
|
||||||
if (authRequest.loginHint) {
|
|
||||||
return s.factors?.user?.loginName === authRequest.loginHint;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sessionsWithHint.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by change date descending
|
|
||||||
sessionsWithHint.sort((a, b) => {
|
|
||||||
const dateA = a.changeDate ? timestampDate(a.changeDate).getTime() : 0;
|
|
||||||
const dateB = b.changeDate ? timestampDate(b.changeDate).getTime() : 0;
|
|
||||||
return dateB - dateA;
|
|
||||||
});
|
|
||||||
|
|
||||||
// return the first valid session according to settings
|
|
||||||
for (const session of sessionsWithHint) {
|
|
||||||
if (await isSessionValid(serviceUrl, session)) {
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function constructUrl(request: NextRequest, path: string) {
|
|
||||||
const forwardedHost =
|
|
||||||
request.headers.get("x-zitadel-forward-host") ??
|
|
||||||
request.headers.get("host");
|
|
||||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
|
|
||||||
return new URL(
|
|
||||||
`${basePath}${path}`,
|
|
||||||
forwardedHost?.startsWith("http")
|
|
||||||
? forwardedHost
|
|
||||||
: `https://${forwardedHost}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
const searchParams = request.nextUrl.searchParams;
|
|
||||||
const authRequestId = searchParams.get("authRequest");
|
|
||||||
const sessionId = searchParams.get("sessionId");
|
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
|
const searchParams = request.nextUrl.searchParams;
|
||||||
|
|
||||||
|
const oidcRequestId = searchParams.get("authRequest"); // oidc initiated request
|
||||||
|
const samlRequestId = searchParams.get("samlRequest"); // saml initiated request
|
||||||
|
|
||||||
|
// internal request id which combines authRequest and samlRequest with the prefix oidc_ or saml_
|
||||||
|
let requestId =
|
||||||
|
searchParams.get("requestId") ||
|
||||||
|
`oidc_${oidcRequestId}` ||
|
||||||
|
`saml_${samlRequestId}`;
|
||||||
|
|
||||||
|
const sessionId = searchParams.get("sessionId");
|
||||||
|
|
||||||
// TODO: find a better way to handle _rsc (react server components) requests and block them to avoid conflicts when creating oidc callback
|
// TODO: find a better way to handle _rsc (react server components) requests and block them to avoid conflicts when creating oidc callback
|
||||||
const _rsc = searchParams.get("_rsc");
|
const _rsc = searchParams.get("_rsc");
|
||||||
if (_rsc) {
|
if (_rsc) {
|
||||||
@@ -232,128 +100,36 @@ export async function GET(request: NextRequest) {
|
|||||||
sessions = await loadSessions({ serviceUrl, ids });
|
sessions = await loadSessions({ serviceUrl, ids });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId && sessionId) {
|
// complete flow if session and request id are provided
|
||||||
console.log(
|
if (requestId && sessionId) {
|
||||||
`Login with session: ${sessionId} and authRequest: ${authRequestId}`,
|
if (requestId.startsWith("oidc_")) {
|
||||||
);
|
// this finishes the login process for OIDC
|
||||||
|
return loginWithOIDCandSession({
|
||||||
const selectedSession = sessions.find((s) => s.id === sessionId);
|
|
||||||
|
|
||||||
if (selectedSession && selectedSession.id) {
|
|
||||||
console.log(`Found session ${selectedSession.id}`);
|
|
||||||
|
|
||||||
const isValid = await isSessionValid(
|
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
authRequest: requestId.replace("oidc_", ""),
|
||||||
selectedSession,
|
sessionId,
|
||||||
);
|
sessions,
|
||||||
|
sessionCookies,
|
||||||
console.log("Session is valid:", isValid);
|
request,
|
||||||
|
|
||||||
if (!isValid && selectedSession.factors?.user) {
|
|
||||||
// if the session is not valid anymore, we need to redirect the user to re-authenticate /
|
|
||||||
// TODO: handle IDP intent direcly if available
|
|
||||||
const command: SendLoginnameCommand = {
|
|
||||||
loginName: selectedSession.factors.user?.loginName,
|
|
||||||
organization: selectedSession.factors?.user?.organizationId,
|
|
||||||
authRequestId: authRequestId,
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await sendLoginname(command);
|
|
||||||
|
|
||||||
if (res && "redirect" in res && res?.redirect) {
|
|
||||||
const absoluteUrl = new URL(res.redirect, request.nextUrl);
|
|
||||||
return NextResponse.redirect(absoluteUrl.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cookie = sessionCookies.find(
|
|
||||||
(cookie) => cookie.id === selectedSession?.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cookie && cookie.id && cookie.token) {
|
|
||||||
const session = {
|
|
||||||
sessionId: cookie?.id,
|
|
||||||
sessionToken: cookie?.token,
|
|
||||||
};
|
|
||||||
|
|
||||||
// works not with _rsc request
|
|
||||||
try {
|
|
||||||
const { callbackUrl } = await createCallback({
|
|
||||||
serviceUrl,
|
|
||||||
|
|
||||||
req: create(CreateCallbackRequestSchema, {
|
|
||||||
authRequestId,
|
|
||||||
callbackKind: {
|
|
||||||
case: "session",
|
|
||||||
value: create(SessionSchema, session),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
if (callbackUrl) {
|
} else if (requestId.startsWith("saml_")) {
|
||||||
return NextResponse.redirect(callbackUrl);
|
// this finishes the login process for SAML
|
||||||
} else {
|
return loginWithSAMLandSession({
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "An error occurred!" },
|
|
||||||
{ status: 500 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
|
||||||
// handle already handled gracefully as these could come up if old emails with authRequestId are used (reset password, register emails etc.)
|
|
||||||
console.error(error);
|
|
||||||
if (
|
|
||||||
error &&
|
|
||||||
typeof error === "object" &&
|
|
||||||
"code" in error &&
|
|
||||||
error?.code === 9
|
|
||||||
) {
|
|
||||||
const loginSettings = await getLoginSettings({
|
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
samlRequest: requestId.replace("saml_", ""),
|
||||||
organization: selectedSession.factors?.user?.organizationId,
|
sessionId,
|
||||||
|
sessions,
|
||||||
|
sessionCookies,
|
||||||
|
request,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loginSettings?.defaultRedirectUri) {
|
|
||||||
return NextResponse.redirect(loginSettings.defaultRedirectUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
const signedinUrl = constructUrl(request, "/signedin");
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
|
|
||||||
if (selectedSession.factors?.user?.loginName) {
|
|
||||||
params.append(
|
|
||||||
"loginName",
|
|
||||||
selectedSession.factors?.user?.loginName,
|
|
||||||
);
|
|
||||||
// signedinUrl.searchParams.set(
|
|
||||||
// "loginName",
|
|
||||||
// selectedSession.factors?.user?.loginName,
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
if (selectedSession.factors?.user?.organizationId) {
|
|
||||||
params.append(
|
|
||||||
"organization",
|
|
||||||
selectedSession.factors?.user?.organizationId,
|
|
||||||
);
|
|
||||||
// signedinUrl.searchParams.set(
|
|
||||||
// "organization",
|
|
||||||
// selectedSession.factors?.user?.organizationId,
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
return NextResponse.redirect(signedinUrl + "?" + params);
|
|
||||||
} else {
|
|
||||||
return NextResponse.json({ error }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
// continue with OIDC
|
||||||
|
if (requestId && requestId.startsWith("oidc_")) {
|
||||||
const { authRequest } = await getAuthRequest({
|
const { authRequest } = await getAuthRequest({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
authRequestId: requestId.replace("oidc_", ""),
|
||||||
authRequestId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let organization = "";
|
let organization = "";
|
||||||
@@ -400,7 +176,6 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
const identityProviders = await getActiveIdentityProviders({
|
const identityProviders = await getActiveIdentityProviders({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
orgId: organization ? organization : undefined,
|
orgId: organization ? organization : undefined,
|
||||||
}).then((resp) => {
|
}).then((resp) => {
|
||||||
return resp.identityProviders;
|
return resp.identityProviders;
|
||||||
@@ -416,8 +191,8 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.set("authRequestId", authRequestId);
|
params.set("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization) {
|
if (organization) {
|
||||||
@@ -426,7 +201,6 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
return startIdentityProviderFlow({
|
return startIdentityProviderFlow({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
idpId,
|
idpId,
|
||||||
urls: {
|
urls: {
|
||||||
successUrl:
|
successUrl:
|
||||||
@@ -448,41 +222,27 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const gotoAccounts = (): NextResponse<unknown> => {
|
|
||||||
const accountsUrl = constructUrl(request, "/accounts");
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
if (authRequest?.id) {
|
|
||||||
params.append("authRequestId", authRequest.id);
|
|
||||||
// accountsUrl.searchParams.set("authRequestId", authRequest?.id);
|
|
||||||
}
|
|
||||||
if (organization) {
|
|
||||||
params.append("organization", organization);
|
|
||||||
// accountsUrl.searchParams.set("organization", organization);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.redirect(accountsUrl + "?" + params);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (authRequest && authRequest.prompt.includes(Prompt.CREATE)) {
|
if (authRequest && authRequest.prompt.includes(Prompt.CREATE)) {
|
||||||
const registerUrl = constructUrl(request, "/register");
|
const registerUrl = new URL("/register", request.url);
|
||||||
const params = new URLSearchParams();
|
|
||||||
if (authRequest.id) {
|
if (authRequest.id) {
|
||||||
params.append("authRequestId", authRequest.id);
|
registerUrl.searchParams.set("requestId", `oidc_${authRequest.id}`);
|
||||||
// registerUrl.searchParams.set("authRequestId", authRequest.id);
|
|
||||||
}
|
}
|
||||||
if (organization) {
|
if (organization) {
|
||||||
params.append("organization", organization);
|
registerUrl.searchParams.set("organization", organization);
|
||||||
// registerUrl.searchParams.set("organization", organization);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.redirect(registerUrl + "?" + params);
|
return NextResponse.redirect(registerUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// use existing session and hydrate it for oidc
|
// use existing session and hydrate it for oidc
|
||||||
if (authRequest && sessions.length) {
|
if (authRequest && sessions.length) {
|
||||||
// if some accounts are available for selection and select_account is set
|
// if some accounts are available for selection and select_account is set
|
||||||
if (authRequest.prompt.includes(Prompt.SELECT_ACCOUNT)) {
|
if (authRequest.prompt.includes(Prompt.SELECT_ACCOUNT)) {
|
||||||
return gotoAccounts();
|
return gotoAccounts({
|
||||||
|
request,
|
||||||
|
requestId: `oidc_${authRequest.id}`,
|
||||||
|
organization,
|
||||||
|
});
|
||||||
} else if (authRequest.prompt.includes(Prompt.LOGIN)) {
|
} else if (authRequest.prompt.includes(Prompt.LOGIN)) {
|
||||||
/**
|
/**
|
||||||
* The login prompt instructs the authentication server to prompt the user for re-authentication, regardless of whether the user is already authenticated
|
* The login prompt instructs the authentication server to prompt the user for re-authentication, regardless of whether the user is already authenticated
|
||||||
@@ -493,7 +253,7 @@ export async function GET(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
let command: SendLoginnameCommand = {
|
let command: SendLoginnameCommand = {
|
||||||
loginName: authRequest.loginHint,
|
loginName: authRequest.loginHint,
|
||||||
authRequestId: authRequest.id,
|
requestId: authRequest.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (organization) {
|
if (organization) {
|
||||||
@@ -503,7 +263,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const res = await sendLoginname(command);
|
const res = await sendLoginname(command);
|
||||||
|
|
||||||
if (res && "redirect" in res && res?.redirect) {
|
if (res && "redirect" in res && res?.redirect) {
|
||||||
const absoluteUrl = new URL(res.redirect, request.nextUrl);
|
const absoluteUrl = new URL(res.redirect, request.url);
|
||||||
return NextResponse.redirect(absoluteUrl.toString());
|
return NextResponse.redirect(absoluteUrl.toString());
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -511,39 +271,31 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginNameUrl = constructUrl(request, "/loginname");
|
const loginNameUrl = new URL("/loginname", request.url);
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
|
|
||||||
if (authRequest.id) {
|
if (authRequest.id) {
|
||||||
params.append("authRequestId", authRequest.id);
|
loginNameUrl.searchParams.set("requestId", `oidc_${authRequest.id}`);
|
||||||
// loginNameUrl.searchParams.set("authRequestId", authRequest.id);
|
|
||||||
}
|
}
|
||||||
if (authRequest.loginHint) {
|
if (authRequest.loginHint) {
|
||||||
params.append("loginName", authRequest.loginHint);
|
loginNameUrl.searchParams.set("loginName", authRequest.loginHint);
|
||||||
// loginNameUrl.searchParams.set("loginName", authRequest.loginHint);
|
|
||||||
}
|
}
|
||||||
if (organization) {
|
if (organization) {
|
||||||
params.append("organization", organization);
|
loginNameUrl.searchParams.set("organization", organization);
|
||||||
// loginNameUrl.searchParams.set("organization", organization);
|
|
||||||
}
|
}
|
||||||
if (suffix) {
|
if (suffix) {
|
||||||
params.append("suffix", suffix);
|
loginNameUrl.searchParams.set("suffix", suffix);
|
||||||
// loginNameUrl.searchParams.set("suffix", suffix);
|
|
||||||
}
|
}
|
||||||
return NextResponse.redirect(loginNameUrl + "?" + params);
|
return NextResponse.redirect(loginNameUrl);
|
||||||
} else if (authRequest.prompt.includes(Prompt.NONE)) {
|
} else if (authRequest.prompt.includes(Prompt.NONE)) {
|
||||||
/**
|
/**
|
||||||
* With an OIDC none prompt, the authentication server must not display any authentication or consent user interface pages.
|
* With an OIDC none prompt, the authentication server must not display any authentication or consent user interface pages.
|
||||||
* This means that the user should not be prompted to enter their password again.
|
* This means that the user should not be prompted to enter their password again.
|
||||||
* Instead, the server attempts to silently authenticate the user using an existing session or other authentication mechanisms that do not require user interaction
|
* Instead, the server attempts to silently authenticate the user using an existing session or other authentication mechanisms that do not require user interaction
|
||||||
**/
|
**/
|
||||||
const selectedSession = await findValidSession(
|
const selectedSession = await findValidSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
sessions,
|
sessions,
|
||||||
authRequest,
|
authRequest,
|
||||||
);
|
});
|
||||||
|
|
||||||
if (!selectedSession || !selectedSession.id) {
|
if (!selectedSession || !selectedSession.id) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@@ -570,9 +322,8 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
const { callbackUrl } = await createCallback({
|
const { callbackUrl } = await createCallback({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
req: create(CreateCallbackRequestSchema, {
|
req: create(CreateCallbackRequestSchema, {
|
||||||
authRequestId,
|
authRequestId: requestId.replace("oidc_", ""),
|
||||||
callbackKind: {
|
callbackKind: {
|
||||||
case: "session",
|
case: "session",
|
||||||
value: create(SessionSchema, session),
|
value: create(SessionSchema, session),
|
||||||
@@ -582,15 +333,18 @@ export async function GET(request: NextRequest) {
|
|||||||
return NextResponse.redirect(callbackUrl);
|
return NextResponse.redirect(callbackUrl);
|
||||||
} else {
|
} else {
|
||||||
// check for loginHint, userId hint and valid sessions
|
// check for loginHint, userId hint and valid sessions
|
||||||
let selectedSession = await findValidSession(
|
let selectedSession = await findValidSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
sessions,
|
sessions,
|
||||||
authRequest,
|
authRequest,
|
||||||
);
|
});
|
||||||
|
|
||||||
if (!selectedSession || !selectedSession.id) {
|
if (!selectedSession || !selectedSession.id) {
|
||||||
return gotoAccounts();
|
return gotoAccounts({
|
||||||
|
request,
|
||||||
|
requestId: `oidc_${authRequest.id}`,
|
||||||
|
organization,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const cookie = sessionCookies.find(
|
const cookie = sessionCookies.find(
|
||||||
@@ -598,7 +352,11 @@ export async function GET(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!cookie || !cookie.id || !cookie.token) {
|
if (!cookie || !cookie.id || !cookie.token) {
|
||||||
return gotoAccounts();
|
return gotoAccounts({
|
||||||
|
request,
|
||||||
|
requestId: `oidc_${authRequest.id}`,
|
||||||
|
organization,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = {
|
const session = {
|
||||||
@@ -611,7 +369,7 @@ export async function GET(request: NextRequest) {
|
|||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
req: create(CreateCallbackRequestSchema, {
|
req: create(CreateCallbackRequestSchema, {
|
||||||
authRequestId,
|
authRequestId: requestId.replace("oidc_", ""),
|
||||||
callbackKind: {
|
callbackKind: {
|
||||||
case: "session",
|
case: "session",
|
||||||
value: create(SessionSchema, session),
|
value: create(SessionSchema, session),
|
||||||
@@ -624,36 +382,148 @@ export async function GET(request: NextRequest) {
|
|||||||
console.log(
|
console.log(
|
||||||
"could not create callback, redirect user to choose other account",
|
"could not create callback, redirect user to choose other account",
|
||||||
);
|
);
|
||||||
return gotoAccounts();
|
return gotoAccounts({
|
||||||
|
request,
|
||||||
|
organization,
|
||||||
|
requestId: `oidc_${authRequest.id}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return gotoAccounts();
|
return gotoAccounts({
|
||||||
|
request,
|
||||||
|
requestId: `oidc_${authRequest.id}`,
|
||||||
|
organization,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const loginNameUrl = constructUrl(request, "/loginname");
|
const loginNameUrl = new URL("/loginname", request.url);
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
loginNameUrl.searchParams.set("requestId", requestId);
|
||||||
params.set("authRequestId", authRequestId);
|
|
||||||
// loginNameUrl.searchParams.set("authRequestId", authRequestId);
|
|
||||||
if (authRequest?.loginHint) {
|
if (authRequest?.loginHint) {
|
||||||
params.set("loginName", authRequest.loginHint);
|
loginNameUrl.searchParams.set("loginName", authRequest.loginHint);
|
||||||
params.set("submit", "true"); // autosubmit
|
loginNameUrl.searchParams.set("submit", "true"); // autosubmit
|
||||||
// loginNameUrl.searchParams.set("loginName", authRequest.loginHint);
|
|
||||||
// loginNameUrl.searchParams.set("submit", "true"); // autosubmit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization) {
|
if (organization) {
|
||||||
params.set("organization", organization);
|
loginNameUrl.searchParams.append("organization", organization);
|
||||||
// loginNameUrl.searchParams.set("organization", organization);
|
// loginNameUrl.searchParams.set("organization", organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.redirect(loginNameUrl + "?" + params);
|
return NextResponse.redirect(loginNameUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// continue with SAML
|
||||||
|
else if (requestId && requestId.startsWith("saml_")) {
|
||||||
|
const { samlRequest } = await getSAMLRequest({
|
||||||
|
serviceUrl,
|
||||||
|
samlRequestId: requestId.replace("saml_", ""),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!samlRequest) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "No samlRequest found" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedSession = await findValidSession({
|
||||||
|
serviceUrl,
|
||||||
|
sessions,
|
||||||
|
samlRequest,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!selectedSession || !selectedSession.id) {
|
||||||
|
return gotoAccounts({
|
||||||
|
request,
|
||||||
|
requestId: `saml_${samlRequest.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookie = sessionCookies.find(
|
||||||
|
(cookie) => cookie.id === selectedSession.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!cookie || !cookie.id || !cookie.token) {
|
||||||
|
return gotoAccounts({
|
||||||
|
request,
|
||||||
|
requestId: `saml_${samlRequest.id}`,
|
||||||
|
// organization,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = {
|
||||||
|
sessionId: cookie.id,
|
||||||
|
sessionToken: cookie.token,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { url, binding } = await createResponse({
|
||||||
|
serviceUrl,
|
||||||
|
req: create(CreateResponseRequestSchema, {
|
||||||
|
samlRequestId: requestId.replace("saml_", ""),
|
||||||
|
responseKind: {
|
||||||
|
case: "session",
|
||||||
|
value: session,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (url && binding.case === "redirect") {
|
||||||
|
return NextResponse.redirect(url);
|
||||||
|
} else if (url && binding.case === "post") {
|
||||||
|
const formData = {
|
||||||
|
key1: "value1",
|
||||||
|
key2: "value2",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert form data to URL-encoded string
|
||||||
|
const formBody = Object.entries(formData)
|
||||||
|
.map(
|
||||||
|
([key, value]) =>
|
||||||
|
encodeURIComponent(key) + "=" + encodeURIComponent(value),
|
||||||
|
)
|
||||||
|
.join("&");
|
||||||
|
|
||||||
|
// Make a POST request to the external URL with the form data
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: formBody,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle the response from the external URL
|
||||||
|
if (response.ok) {
|
||||||
|
return NextResponse.json({
|
||||||
|
message: "SAML request completed successfully",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Failed to complete SAML request" },
|
||||||
|
{ status: response.status },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"could not create response, redirect user to choose other account",
|
||||||
|
);
|
||||||
|
return gotoAccounts({
|
||||||
|
request,
|
||||||
|
requestId: `saml_${samlRequest.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return gotoAccounts({
|
||||||
|
request,
|
||||||
|
requestId: `saml_${samlRequest.id}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "No authRequestId provided" },
|
{ error: "No authRequest nor samlRequest provided" },
|
||||||
{ status: 500 },
|
{ status: 500 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -35,7 +35,7 @@ type Props = {
|
|||||||
passwordComplexitySettings: PasswordComplexitySettings;
|
passwordComplexitySettings: PasswordComplexitySettings;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
loginName: string;
|
loginName: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ export function ChangePasswordForm({
|
|||||||
passwordComplexitySettings,
|
passwordComplexitySettings,
|
||||||
sessionId,
|
sessionId,
|
||||||
loginName,
|
loginName,
|
||||||
authRequestId,
|
requestId,
|
||||||
organization,
|
organization,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const t = useTranslations("password");
|
const t = useTranslations("password");
|
||||||
@@ -97,7 +97,7 @@ export function ChangePasswordForm({
|
|||||||
checks: create(ChecksSchema, {
|
checks: create(ChecksSchema, {
|
||||||
password: { password: values.password },
|
password: { password: values.password },
|
||||||
}),
|
}),
|
||||||
authRequestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not verify password");
|
setError("Could not verify password");
|
||||||
|
@@ -1,35 +1,44 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { skipMFAAndContinueWithNextUrl } from "@/lib/server/session";
|
||||||
import {
|
import {
|
||||||
LoginSettings,
|
LoginSettings,
|
||||||
SecondFactorType,
|
SecondFactorType,
|
||||||
} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import { EMAIL, SMS, TOTP, U2F } from "./auth-methods";
|
import { EMAIL, SMS, TOTP, U2F } from "./auth-methods";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
userId: string;
|
||||||
loginName?: string;
|
loginName?: string;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
loginSettings: LoginSettings;
|
loginSettings: LoginSettings;
|
||||||
userMethods: AuthenticationMethodType[];
|
userMethods: AuthenticationMethodType[];
|
||||||
checkAfter: boolean;
|
checkAfter: boolean;
|
||||||
phoneVerified: boolean;
|
phoneVerified: boolean;
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
|
force: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ChooseSecondFactorToSetup({
|
export function ChooseSecondFactorToSetup({
|
||||||
|
userId,
|
||||||
loginName,
|
loginName,
|
||||||
sessionId,
|
sessionId,
|
||||||
authRequestId,
|
requestId,
|
||||||
organization,
|
organization,
|
||||||
loginSettings,
|
loginSettings,
|
||||||
userMethods,
|
userMethods,
|
||||||
checkAfter,
|
checkAfter,
|
||||||
phoneVerified,
|
phoneVerified,
|
||||||
emailVerified,
|
emailVerified,
|
||||||
|
force,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const t = useTranslations("mfa");
|
||||||
|
const router = useRouter();
|
||||||
const params = new URLSearchParams({});
|
const params = new URLSearchParams({});
|
||||||
|
|
||||||
if (loginName) {
|
if (loginName) {
|
||||||
@@ -38,8 +47,8 @@ export function ChooseSecondFactorToSetup({
|
|||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
params.append("sessionId", sessionId);
|
params.append("sessionId", sessionId);
|
||||||
}
|
}
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.append("authRequestId", authRequestId);
|
params.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
if (organization) {
|
if (organization) {
|
||||||
params.append("organization", organization);
|
params.append("organization", organization);
|
||||||
@@ -49,6 +58,7 @@ export function ChooseSecondFactorToSetup({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="grid grid-cols-1 gap-5 w-full pt-4">
|
<div className="grid grid-cols-1 gap-5 w-full pt-4">
|
||||||
{loginSettings.secondFactors.map((factor) => {
|
{loginSettings.secondFactors.map((factor) => {
|
||||||
switch (factor) {
|
switch (factor) {
|
||||||
@@ -83,5 +93,28 @@ export function ChooseSecondFactorToSetup({
|
|||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{!force && (
|
||||||
|
<button
|
||||||
|
className="transition-all text-sm hover:text-primary-light-500 dark:hover:text-primary-dark-500"
|
||||||
|
onClick={async () => {
|
||||||
|
const resp = await skipMFAAndContinueWithNextUrl({
|
||||||
|
userId,
|
||||||
|
loginName,
|
||||||
|
sessionId,
|
||||||
|
organization,
|
||||||
|
requestId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp?.redirect) {
|
||||||
|
return router.push(resp.redirect);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
data-testid="reset-button"
|
||||||
|
>
|
||||||
|
{t("set.skip")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { EMAIL, SMS, TOTP, U2F } from "./auth-methods";
|
|||||||
type Props = {
|
type Props = {
|
||||||
loginName?: string;
|
loginName?: string;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
userMethods: AuthenticationMethodType[];
|
userMethods: AuthenticationMethodType[];
|
||||||
};
|
};
|
||||||
@@ -14,7 +14,7 @@ type Props = {
|
|||||||
export function ChooseSecondFactor({
|
export function ChooseSecondFactor({
|
||||||
loginName,
|
loginName,
|
||||||
sessionId,
|
sessionId,
|
||||||
authRequestId,
|
requestId,
|
||||||
organization,
|
organization,
|
||||||
userMethods,
|
userMethods,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
@@ -26,8 +26,8 @@ export function ChooseSecondFactor({
|
|||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
params.append("sessionId", sessionId);
|
params.append("sessionId", sessionId);
|
||||||
}
|
}
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.append("authRequestId", authRequestId);
|
params.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
if (organization) {
|
if (organization) {
|
||||||
params.append("organization", organization);
|
params.append("organization", organization);
|
||||||
|
@@ -13,13 +13,13 @@ type Props = {
|
|||||||
idpIntentId: string;
|
idpIntentId: string;
|
||||||
idpIntentToken: string;
|
idpIntentToken: string;
|
||||||
};
|
};
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function IdpSignin({
|
export function IdpSignin({
|
||||||
userId,
|
userId,
|
||||||
idpIntent: { idpIntentId, idpIntentToken },
|
idpIntent: { idpIntentId, idpIntentToken },
|
||||||
authRequestId,
|
requestId,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -33,7 +33,7 @@ export function IdpSignin({
|
|||||||
idpIntentId,
|
idpIntentId,
|
||||||
idpIntentToken,
|
idpIntentToken,
|
||||||
},
|
},
|
||||||
authRequestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response && "error" in response && response?.error) {
|
if (response && "error" in response && response?.error) {
|
||||||
|
@@ -6,7 +6,7 @@ import { IdpSignin } from "../../idp-signin";
|
|||||||
export async function linkingSuccess(
|
export async function linkingSuccess(
|
||||||
userId: string,
|
userId: string,
|
||||||
idpIntent: { idpIntentId: string; idpIntentToken: string },
|
idpIntent: { idpIntentId: string; idpIntentToken: string },
|
||||||
authRequestId?: string,
|
requestId?: string,
|
||||||
branding?: BrandingSettings,
|
branding?: BrandingSettings,
|
||||||
) {
|
) {
|
||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
@@ -21,7 +21,7 @@ export async function linkingSuccess(
|
|||||||
<IdpSignin
|
<IdpSignin
|
||||||
userId={userId}
|
userId={userId}
|
||||||
idpIntent={idpIntent}
|
idpIntent={idpIntent}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DynamicTheme>
|
</DynamicTheme>
|
||||||
|
@@ -6,7 +6,7 @@ import { IdpSignin } from "../../idp-signin";
|
|||||||
export async function loginSuccess(
|
export async function loginSuccess(
|
||||||
userId: string,
|
userId: string,
|
||||||
idpIntent: { idpIntentId: string; idpIntentToken: string },
|
idpIntent: { idpIntentId: string; idpIntentToken: string },
|
||||||
authRequestId?: string,
|
requestId?: string,
|
||||||
branding?: BrandingSettings,
|
branding?: BrandingSettings,
|
||||||
) {
|
) {
|
||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
@@ -21,7 +21,7 @@ export async function loginSuccess(
|
|||||||
<IdpSignin
|
<IdpSignin
|
||||||
userId={userId}
|
userId={userId}
|
||||||
idpIntent={idpIntent}
|
idpIntent={idpIntent}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DynamicTheme>
|
</DynamicTheme>
|
||||||
|
@@ -21,7 +21,7 @@ type Props = {
|
|||||||
host: string | null;
|
host: string | null;
|
||||||
loginName?: string;
|
loginName?: string;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
method: string;
|
method: string;
|
||||||
code?: string;
|
code?: string;
|
||||||
@@ -36,7 +36,7 @@ export function LoginOTP({
|
|||||||
host,
|
host,
|
||||||
loginName,
|
loginName,
|
||||||
sessionId,
|
sessionId,
|
||||||
authRequestId,
|
requestId,
|
||||||
organization,
|
organization,
|
||||||
method,
|
method,
|
||||||
code,
|
code,
|
||||||
@@ -87,7 +87,7 @@ export function LoginOTP({
|
|||||||
? {
|
? {
|
||||||
urlTemplate:
|
urlTemplate:
|
||||||
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/otp/${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}` +
|
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/otp/${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}` +
|
||||||
(authRequestId ? `&authRequestId=${authRequestId}` : ""),
|
(requestId ? `&requestId=${requestId}` : ""),
|
||||||
}
|
}
|
||||||
: {},
|
: {},
|
||||||
},
|
},
|
||||||
@@ -107,7 +107,7 @@ export function LoginOTP({
|
|||||||
sessionId,
|
sessionId,
|
||||||
organization,
|
organization,
|
||||||
challenges,
|
challenges,
|
||||||
authRequestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not request OTP challenge");
|
setError("Could not request OTP challenge");
|
||||||
@@ -137,8 +137,8 @@ export function LoginOTP({
|
|||||||
body.organization = organization;
|
body.organization = organization;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
body.authRequestId = authRequestId;
|
body.requestId = requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
let checks;
|
let checks;
|
||||||
@@ -164,7 +164,7 @@ export function LoginOTP({
|
|||||||
sessionId,
|
sessionId,
|
||||||
organization,
|
organization,
|
||||||
checks,
|
checks,
|
||||||
authRequestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not verify OTP code");
|
setError("Could not verify OTP code");
|
||||||
@@ -190,11 +190,11 @@ export function LoginOTP({
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
const url =
|
const url =
|
||||||
authRequestId && response.sessionId
|
requestId && response.sessionId
|
||||||
? await getNextUrl(
|
? await getNextUrl(
|
||||||
{
|
{
|
||||||
sessionId: response.sessionId,
|
sessionId: response.sessionId,
|
||||||
authRequestId: authRequestId,
|
requestId: requestId,
|
||||||
organization: response.factors?.user?.organizationId,
|
organization: response.factors?.user?.organizationId,
|
||||||
},
|
},
|
||||||
loginSettings?.defaultRedirectUri,
|
loginSettings?.defaultRedirectUri,
|
||||||
|
@@ -21,7 +21,7 @@ import { Spinner } from "./spinner";
|
|||||||
type Props = {
|
type Props = {
|
||||||
loginName?: string;
|
loginName?: string;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
altPassword: boolean;
|
altPassword: boolean;
|
||||||
login?: boolean;
|
login?: boolean;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
@@ -30,7 +30,7 @@ type Props = {
|
|||||||
export function LoginPasskey({
|
export function LoginPasskey({
|
||||||
loginName,
|
loginName,
|
||||||
sessionId,
|
sessionId,
|
||||||
authRequestId,
|
requestId,
|
||||||
altPassword,
|
altPassword,
|
||||||
organization,
|
organization,
|
||||||
login = true,
|
login = true,
|
||||||
@@ -96,7 +96,7 @@ export function LoginPasskey({
|
|||||||
userVerificationRequirement,
|
userVerificationRequirement,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
authRequestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not request passkey challenge");
|
setError("Could not request passkey challenge");
|
||||||
@@ -123,7 +123,7 @@ export function LoginPasskey({
|
|||||||
checks: {
|
checks: {
|
||||||
webAuthN: { credentialAssertionData: data },
|
webAuthN: { credentialAssertionData: data },
|
||||||
} as Checks,
|
} as Checks,
|
||||||
authRequestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not verify passkey");
|
setError("Could not verify passkey");
|
||||||
@@ -220,8 +220,8 @@ export function LoginPasskey({
|
|||||||
params.sessionId = sessionId;
|
params.sessionId = sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.authRequestId = authRequestId;
|
params.requestId = requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization) {
|
if (organization) {
|
||||||
|
@@ -22,7 +22,7 @@ type Props = {
|
|||||||
loginSettings: LoginSettings | undefined;
|
loginSettings: LoginSettings | undefined;
|
||||||
loginName: string;
|
loginName: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
isAlternative?: boolean; // whether password was requested as alternative auth method
|
isAlternative?: boolean; // whether password was requested as alternative auth method
|
||||||
promptPasswordless?: boolean;
|
promptPasswordless?: boolean;
|
||||||
};
|
};
|
||||||
@@ -31,7 +31,7 @@ export function PasswordForm({
|
|||||||
loginSettings,
|
loginSettings,
|
||||||
loginName,
|
loginName,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
promptPasswordless,
|
promptPasswordless,
|
||||||
isAlternative,
|
isAlternative,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
@@ -58,7 +58,7 @@ export function PasswordForm({
|
|||||||
checks: create(ChecksSchema, {
|
checks: create(ChecksSchema, {
|
||||||
password: { password: values.password },
|
password: { password: values.password },
|
||||||
}),
|
}),
|
||||||
authRequestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not verify password");
|
setError("Could not verify password");
|
||||||
@@ -86,7 +86,7 @@ export function PasswordForm({
|
|||||||
const response = await resetPassword({
|
const response = await resetPassword({
|
||||||
loginName,
|
loginName,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not reset password");
|
setError("Could not reset password");
|
||||||
@@ -111,8 +111,8 @@ export function PasswordForm({
|
|||||||
params.append("organization", organization);
|
params.append("organization", organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.append("authRequestId", authRequestId);
|
params.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return router.push("/password/set?" + params);
|
return router.push("/password/set?" + params);
|
||||||
|
@@ -36,7 +36,7 @@ type Props = {
|
|||||||
lastname?: string;
|
lastname?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
loginSettings?: LoginSettings;
|
loginSettings?: LoginSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export function RegisterForm({
|
|||||||
firstname,
|
firstname,
|
||||||
lastname,
|
lastname,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
loginSettings,
|
loginSettings,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const t = useTranslations("register");
|
const t = useTranslations("register");
|
||||||
@@ -73,7 +73,7 @@ export function RegisterForm({
|
|||||||
firstName: values.firstname,
|
firstName: values.firstname,
|
||||||
lastName: values.lastname,
|
lastName: values.lastname,
|
||||||
organization: organization,
|
organization: organization,
|
||||||
authRequestId: authRequestId,
|
requestId: requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not register user");
|
setError("Could not register user");
|
||||||
@@ -105,8 +105,8 @@ export function RegisterForm({
|
|||||||
registerParams.organization = organization;
|
registerParams.organization = organization;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
registerParams.authRequestId = authRequestId;
|
registerParams.requestId = requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect user to /register/password if password is chosen
|
// redirect user to /register/password if password is chosen
|
||||||
|
@@ -19,7 +19,7 @@ type Inputs = {};
|
|||||||
type Props = {
|
type Props = {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
isPrompt: boolean;
|
isPrompt: boolean;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ export function RegisterPasskey({
|
|||||||
sessionId,
|
sessionId,
|
||||||
isPrompt,
|
isPrompt,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const t = useTranslations("passkey");
|
const t = useTranslations("passkey");
|
||||||
|
|
||||||
@@ -161,8 +161,8 @@ export function RegisterPasskey({
|
|||||||
params.set("organization", organization);
|
params.set("organization", organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.set("authRequestId", authRequestId);
|
params.set("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
params.set("sessionId", sessionId);
|
params.set("sessionId", sessionId);
|
||||||
|
@@ -16,7 +16,7 @@ import { Spinner } from "./spinner";
|
|||||||
type Props = {
|
type Props = {
|
||||||
loginName?: string;
|
loginName?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
checkAfter: boolean;
|
checkAfter: boolean;
|
||||||
loginSettings?: LoginSettings;
|
loginSettings?: LoginSettings;
|
||||||
@@ -26,7 +26,7 @@ export function RegisterU2f({
|
|||||||
loginName,
|
loginName,
|
||||||
sessionId,
|
sessionId,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
checkAfter,
|
checkAfter,
|
||||||
loginSettings,
|
loginSettings,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
@@ -166,18 +166,18 @@ export function RegisterU2f({
|
|||||||
if (organization) {
|
if (organization) {
|
||||||
paramsToContinue.append("organization", organization);
|
paramsToContinue.append("organization", organization);
|
||||||
}
|
}
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
paramsToContinue.append("authRequestId", authRequestId);
|
paramsToContinue.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return router.push(`/u2f?` + paramsToContinue);
|
return router.push(`/u2f?` + paramsToContinue);
|
||||||
} else {
|
} else {
|
||||||
const url =
|
const url =
|
||||||
authRequestId && sessionId
|
requestId && sessionId
|
||||||
? await getNextUrl(
|
? await getNextUrl(
|
||||||
{
|
{
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
authRequestId: authRequestId,
|
requestId: requestId,
|
||||||
organization: organization,
|
organization: organization,
|
||||||
},
|
},
|
||||||
loginSettings?.defaultRedirectUri,
|
loginSettings?.defaultRedirectUri,
|
||||||
|
@@ -31,11 +31,11 @@ export function isSessionValid(session: Partial<Session>): {
|
|||||||
export function SessionItem({
|
export function SessionItem({
|
||||||
session,
|
session,
|
||||||
reload,
|
reload,
|
||||||
authRequestId,
|
requestId,
|
||||||
}: {
|
}: {
|
||||||
session: Session;
|
session: Session;
|
||||||
reload: () => void;
|
reload: () => void;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ export function SessionItem({
|
|||||||
if (valid && session?.factors?.user) {
|
if (valid && session?.factors?.user) {
|
||||||
const resp = await continueWithSession({
|
const resp = await continueWithSession({
|
||||||
...session,
|
...session,
|
||||||
authRequestId: authRequestId,
|
requestId: requestId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resp?.redirect) {
|
if (resp?.redirect) {
|
||||||
@@ -78,7 +78,7 @@ export function SessionItem({
|
|||||||
const res = await sendLoginname({
|
const res = await sendLoginname({
|
||||||
loginName: session.factors?.user?.loginName,
|
loginName: session.factors?.user?.loginName,
|
||||||
organization: session.factors.user.organizationId,
|
organization: session.factors.user.organizationId,
|
||||||
authRequestId: authRequestId,
|
requestId: requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("An internal error occurred");
|
setError("An internal error occurred");
|
||||||
|
@@ -9,10 +9,10 @@ import { SessionItem } from "./session-item";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
sessions: Session[];
|
sessions: Session[];
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SessionsList({ sessions, authRequestId }: Props) {
|
export function SessionsList({ sessions, requestId }: Props) {
|
||||||
const t = useTranslations("accounts");
|
const t = useTranslations("accounts");
|
||||||
const [list, setList] = useState<Session[]>(sessions);
|
const [list, setList] = useState<Session[]>(sessions);
|
||||||
return sessions ? (
|
return sessions ? (
|
||||||
@@ -34,7 +34,7 @@ export function SessionsList({ sessions, authRequestId }: Props) {
|
|||||||
return (
|
return (
|
||||||
<SessionItem
|
<SessionItem
|
||||||
session={session}
|
session={session}
|
||||||
authRequestId={authRequestId}
|
requestId={requestId}
|
||||||
reload={() => {
|
reload={() => {
|
||||||
setList(list.filter((s) => s.id !== session.id));
|
setList(list.filter((s) => s.id !== session.id));
|
||||||
}}
|
}}
|
||||||
|
@@ -39,14 +39,14 @@ type Props = {
|
|||||||
loginName: string;
|
loginName: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
codeRequired: boolean;
|
codeRequired: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SetPasswordForm({
|
export function SetPasswordForm({
|
||||||
passwordComplexitySettings,
|
passwordComplexitySettings,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
loginName,
|
loginName,
|
||||||
userId,
|
userId,
|
||||||
code,
|
code,
|
||||||
@@ -73,7 +73,7 @@ export function SetPasswordForm({
|
|||||||
const response = await resetPassword({
|
const response = await resetPassword({
|
||||||
loginName,
|
loginName,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not reset password");
|
setError("Could not reset password");
|
||||||
@@ -137,7 +137,7 @@ export function SetPasswordForm({
|
|||||||
checks: create(ChecksSchema, {
|
checks: create(ChecksSchema, {
|
||||||
password: { password: values.password },
|
password: { password: values.password },
|
||||||
}),
|
}),
|
||||||
authRequestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not verify password");
|
setError("Could not verify password");
|
||||||
|
@@ -32,7 +32,7 @@ type Props = {
|
|||||||
firstname: string;
|
firstname: string;
|
||||||
lastname: string;
|
lastname: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SetRegisterPasswordForm({
|
export function SetRegisterPasswordForm({
|
||||||
@@ -41,7 +41,7 @@ export function SetRegisterPasswordForm({
|
|||||||
firstname,
|
firstname,
|
||||||
lastname,
|
lastname,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const t = useTranslations("register");
|
const t = useTranslations("register");
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ export function SetRegisterPasswordForm({
|
|||||||
firstName: firstname,
|
firstName: firstname,
|
||||||
lastName: lastname,
|
lastName: lastname,
|
||||||
organization: organization,
|
organization: organization,
|
||||||
authRequestId: authRequestId,
|
requestId: requestId,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
@@ -20,14 +20,14 @@ import { SignInWithGoogle } from "./idps/sign-in-with-google";
|
|||||||
export interface SignInWithIDPProps {
|
export interface SignInWithIDPProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
identityProviders: IdentityProvider[];
|
identityProviders: IdentityProvider[];
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
linkOnly?: boolean;
|
linkOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SignInWithIdp({
|
export function SignInWithIdp({
|
||||||
identityProviders,
|
identityProviders,
|
||||||
authRequestId,
|
requestId,
|
||||||
organization,
|
organization,
|
||||||
linkOnly,
|
linkOnly,
|
||||||
}: Readonly<SignInWithIDPProps>) {
|
}: Readonly<SignInWithIDPProps>) {
|
||||||
@@ -40,7 +40,7 @@ export function SignInWithIdp({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (linkOnly) params.set("link", "true");
|
if (linkOnly) params.set("link", "true");
|
||||||
if (authRequestId) params.set("authRequestId", authRequestId);
|
if (requestId) params.set("requestId", requestId);
|
||||||
if (organization) params.set("organization", organization);
|
if (organization) params.set("organization", organization);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -64,7 +64,7 @@ export function SignInWithIdp({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[authRequestId, organization, linkOnly, router],
|
[requestId, organization, linkOnly, router],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderIDPButton = (idp: IdentityProvider) => {
|
const renderIDPButton = (idp: IdentityProvider) => {
|
||||||
|
@@ -24,7 +24,7 @@ type Props = {
|
|||||||
secret: string;
|
secret: string;
|
||||||
loginName?: string;
|
loginName?: string;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
checkAfter?: boolean;
|
checkAfter?: boolean;
|
||||||
loginSettings?: LoginSettings;
|
loginSettings?: LoginSettings;
|
||||||
@@ -34,7 +34,7 @@ export function TotpRegister({
|
|||||||
secret,
|
secret,
|
||||||
loginName,
|
loginName,
|
||||||
sessionId,
|
sessionId,
|
||||||
authRequestId,
|
requestId,
|
||||||
organization,
|
organization,
|
||||||
checkAfter,
|
checkAfter,
|
||||||
loginSettings,
|
loginSettings,
|
||||||
@@ -63,8 +63,8 @@ export function TotpRegister({
|
|||||||
if (loginName) {
|
if (loginName) {
|
||||||
params.append("loginName", loginName);
|
params.append("loginName", loginName);
|
||||||
}
|
}
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.append("authRequestId", authRequestId);
|
params.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
if (organization) {
|
if (organization) {
|
||||||
params.append("organization", organization);
|
params.append("organization", organization);
|
||||||
@@ -73,11 +73,11 @@ export function TotpRegister({
|
|||||||
return router.push(`/otp/time-based?` + params);
|
return router.push(`/otp/time-based?` + params);
|
||||||
} else {
|
} else {
|
||||||
const url =
|
const url =
|
||||||
authRequestId && sessionId
|
requestId && sessionId
|
||||||
? await getNextUrl(
|
? await getNextUrl(
|
||||||
{
|
{
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
authRequestId: authRequestId,
|
requestId: requestId,
|
||||||
organization: organization,
|
organization: organization,
|
||||||
},
|
},
|
||||||
loginSettings?.defaultRedirectUri,
|
loginSettings?.defaultRedirectUri,
|
||||||
|
@@ -25,8 +25,8 @@ export function UserAvatar({
|
|||||||
params.set("organization", searchParams.organization);
|
params.set("organization", searchParams.organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchParams?.authRequestId) {
|
if (searchParams?.requestId) {
|
||||||
params.set("authRequestId", searchParams.authRequestId);
|
params.set("requestId", searchParams.requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchParams?.loginName) {
|
if (searchParams?.loginName) {
|
||||||
|
@@ -18,7 +18,7 @@ type Inputs = {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loginName: string | undefined;
|
loginName: string | undefined;
|
||||||
authRequestId: string | undefined;
|
requestId: string | undefined;
|
||||||
loginSettings: LoginSettings | undefined;
|
loginSettings: LoginSettings | undefined;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
@@ -29,7 +29,7 @@ type Props = {
|
|||||||
|
|
||||||
export function UsernameForm({
|
export function UsernameForm({
|
||||||
loginName,
|
loginName,
|
||||||
authRequestId,
|
requestId,
|
||||||
organization,
|
organization,
|
||||||
suffix,
|
suffix,
|
||||||
loginSettings,
|
loginSettings,
|
||||||
@@ -56,7 +56,7 @@ export function UsernameForm({
|
|||||||
const res = await sendLoginname({
|
const res = await sendLoginname({
|
||||||
loginName: values.loginName,
|
loginName: values.loginName,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
suffix,
|
suffix,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -117,8 +117,8 @@ export function UsernameForm({
|
|||||||
if (organization) {
|
if (organization) {
|
||||||
registerParams.append("organization", organization);
|
registerParams.append("organization", organization);
|
||||||
}
|
}
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
registerParams.append("authRequestId", authRequestId);
|
registerParams.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push("/register?" + registerParams);
|
router.push("/register?" + registerParams);
|
||||||
|
@@ -21,14 +21,14 @@ type Props = {
|
|||||||
organization?: string;
|
organization?: string;
|
||||||
code?: string;
|
code?: string;
|
||||||
isInvite: boolean;
|
isInvite: boolean;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function VerifyForm({
|
export function VerifyForm({
|
||||||
userId,
|
userId,
|
||||||
loginName,
|
loginName,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
code,
|
code,
|
||||||
isInvite,
|
isInvite,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
@@ -78,7 +78,7 @@ export function VerifyForm({
|
|||||||
isInvite: isInvite,
|
isInvite: isInvite,
|
||||||
loginName: loginName,
|
loginName: loginName,
|
||||||
organization: organization,
|
organization: organization,
|
||||||
authRequestId: authRequestId,
|
requestId: requestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not verify user");
|
setError("Could not verify user");
|
||||||
|
@@ -15,13 +15,13 @@ import { Spinner } from "./spinner";
|
|||||||
export function VerifyRedirectButton({
|
export function VerifyRedirectButton({
|
||||||
userId,
|
userId,
|
||||||
loginName,
|
loginName,
|
||||||
authRequestId,
|
requestId,
|
||||||
authMethods,
|
authMethods,
|
||||||
organization,
|
organization,
|
||||||
}: {
|
}: {
|
||||||
userId?: string;
|
userId?: string;
|
||||||
loginName?: string;
|
loginName?: string;
|
||||||
authRequestId: string;
|
requestId: string;
|
||||||
authMethods: AuthenticationMethodType[] | null;
|
authMethods: AuthenticationMethodType[] | null;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -35,7 +35,7 @@ export function VerifyRedirectButton({
|
|||||||
|
|
||||||
let command = {
|
let command = {
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
} as SendVerificationRedirectWithoutCheckCommand;
|
} as SendVerificationRedirectWithoutCheckCommand;
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
type FinishFlowCommand =
|
type FinishFlowCommand =
|
||||||
| {
|
| {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
authRequestId: string;
|
requestId: string;
|
||||||
}
|
}
|
||||||
| { loginName: string };
|
| { loginName: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* for client: redirects user back to OIDC application or to a success page when using authRequestId, check if a default redirect and redirect to it, or just redirect to a success page with the loginName
|
* for client: redirects user back to an OIDC or SAML application or to a success page when using requestId, check if a default redirect and redirect to it, or just redirect to a success page with the loginName
|
||||||
* @param command
|
* @param command
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
@@ -14,10 +14,10 @@ export async function getNextUrl(
|
|||||||
command: FinishFlowCommand & { organization?: string },
|
command: FinishFlowCommand & { organization?: string },
|
||||||
defaultRedirectUri?: string,
|
defaultRedirectUri?: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if ("sessionId" in command && "authRequestId" in command) {
|
if ("sessionId" in command && "requestId" in command) {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
sessionId: command.sessionId,
|
sessionId: command.sessionId,
|
||||||
authRequest: command.authRequestId,
|
requestId: command.requestId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (command.organization) {
|
if (command.organization) {
|
||||||
|
@@ -15,7 +15,7 @@ export type Cookie = {
|
|||||||
creationTs: string;
|
creationTs: string;
|
||||||
expirationTs: string;
|
expirationTs: string;
|
||||||
changeTs: string;
|
changeTs: string;
|
||||||
authRequestId?: string; // if its linked to an OIDC flow
|
requestId?: string; // if its linked to an OIDC flow
|
||||||
};
|
};
|
||||||
|
|
||||||
type SessionCookie<T> = Cookie & T;
|
type SessionCookie<T> = Cookie & T;
|
||||||
|
131
apps/login/src/lib/oidc.ts
Normal file
131
apps/login/src/lib/oidc.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import { Cookie } from "@/lib/cookies";
|
||||||
|
import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname";
|
||||||
|
import { createCallback, getLoginSettings } from "@/lib/zitadel";
|
||||||
|
import { create } from "@zitadel/client";
|
||||||
|
import {
|
||||||
|
CreateCallbackRequestSchema,
|
||||||
|
SessionSchema,
|
||||||
|
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||||
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { isSessionValid } from "./session";
|
||||||
|
|
||||||
|
type LoginWithOIDCandSession = {
|
||||||
|
serviceUrl: string;
|
||||||
|
authRequest: string;
|
||||||
|
sessionId: string;
|
||||||
|
sessions: Session[];
|
||||||
|
sessionCookies: Cookie[];
|
||||||
|
request: NextRequest;
|
||||||
|
};
|
||||||
|
export async function loginWithOIDCandSession({
|
||||||
|
serviceUrl,
|
||||||
|
authRequest,
|
||||||
|
sessionId,
|
||||||
|
sessions,
|
||||||
|
sessionCookies,
|
||||||
|
request,
|
||||||
|
}: LoginWithOIDCandSession) {
|
||||||
|
console.log(
|
||||||
|
`Login with session: ${sessionId} and authRequest: ${authRequest}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedSession = sessions.find((s) => s.id === sessionId);
|
||||||
|
|
||||||
|
if (selectedSession && selectedSession.id) {
|
||||||
|
console.log(`Found session ${selectedSession.id}`);
|
||||||
|
|
||||||
|
const isValid = await isSessionValid({
|
||||||
|
serviceUrl,
|
||||||
|
session: selectedSession,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Session is valid:", isValid);
|
||||||
|
|
||||||
|
if (!isValid && selectedSession.factors?.user) {
|
||||||
|
// if the session is not valid anymore, we need to redirect the user to re-authenticate /
|
||||||
|
// TODO: handle IDP intent direcly if available
|
||||||
|
const command: SendLoginnameCommand = {
|
||||||
|
loginName: selectedSession.factors.user?.loginName,
|
||||||
|
organization: selectedSession.factors?.user?.organizationId,
|
||||||
|
requestId: `oidc_${authRequest}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await sendLoginname(command);
|
||||||
|
|
||||||
|
if (res && "redirect" in res && res?.redirect) {
|
||||||
|
const absoluteUrl = new URL(res.redirect, request.url);
|
||||||
|
return NextResponse.redirect(absoluteUrl.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookie = sessionCookies.find(
|
||||||
|
(cookie) => cookie.id === selectedSession?.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cookie && cookie.id && cookie.token) {
|
||||||
|
const session = {
|
||||||
|
sessionId: cookie?.id,
|
||||||
|
sessionToken: cookie?.token,
|
||||||
|
};
|
||||||
|
|
||||||
|
// works not with _rsc request
|
||||||
|
try {
|
||||||
|
const { callbackUrl } = await createCallback({
|
||||||
|
serviceUrl,
|
||||||
|
req: create(CreateCallbackRequestSchema, {
|
||||||
|
authRequestId: authRequest,
|
||||||
|
callbackKind: {
|
||||||
|
case: "session",
|
||||||
|
value: create(SessionSchema, session),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (callbackUrl) {
|
||||||
|
return NextResponse.redirect(callbackUrl);
|
||||||
|
} else {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "An error occurred!" },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
// handle already handled gracefully as these could come up if old emails with requestId are used (reset password, register emails etc.)
|
||||||
|
console.error(error);
|
||||||
|
if (
|
||||||
|
error &&
|
||||||
|
typeof error === "object" &&
|
||||||
|
"code" in error &&
|
||||||
|
error?.code === 9
|
||||||
|
) {
|
||||||
|
const loginSettings = await getLoginSettings({
|
||||||
|
serviceUrl,
|
||||||
|
organization: selectedSession.factors?.user?.organizationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loginSettings?.defaultRedirectUri) {
|
||||||
|
return NextResponse.redirect(loginSettings.defaultRedirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
const signedinUrl = new URL("/signedin", request.url);
|
||||||
|
|
||||||
|
if (selectedSession.factors?.user?.loginName) {
|
||||||
|
signedinUrl.searchParams.set(
|
||||||
|
"loginName",
|
||||||
|
selectedSession.factors?.user?.loginName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (selectedSession.factors?.user?.organizationId) {
|
||||||
|
signedinUrl.searchParams.set(
|
||||||
|
"organization",
|
||||||
|
selectedSession.factors?.user?.organizationId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return NextResponse.redirect(signedinUrl);
|
||||||
|
} else {
|
||||||
|
return NextResponse.json({ error }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
129
apps/login/src/lib/saml.ts
Normal file
129
apps/login/src/lib/saml.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { Cookie } from "@/lib/cookies";
|
||||||
|
import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname";
|
||||||
|
import { createResponse, getLoginSettings } from "@/lib/zitadel";
|
||||||
|
import { create } from "@zitadel/client";
|
||||||
|
import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb";
|
||||||
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { isSessionValid } from "./session";
|
||||||
|
|
||||||
|
type LoginWithSAMLandSession = {
|
||||||
|
serviceUrl: string;
|
||||||
|
samlRequest: string;
|
||||||
|
sessionId: string;
|
||||||
|
sessions: Session[];
|
||||||
|
sessionCookies: Cookie[];
|
||||||
|
request: NextRequest;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function loginWithSAMLandSession({
|
||||||
|
serviceUrl,
|
||||||
|
samlRequest,
|
||||||
|
sessionId,
|
||||||
|
sessions,
|
||||||
|
sessionCookies,
|
||||||
|
request,
|
||||||
|
}: LoginWithSAMLandSession) {
|
||||||
|
console.log(
|
||||||
|
`Login with session: ${sessionId} and samlRequest: ${samlRequest}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedSession = sessions.find((s) => s.id === sessionId);
|
||||||
|
|
||||||
|
if (selectedSession && selectedSession.id) {
|
||||||
|
console.log(`Found session ${selectedSession.id}`);
|
||||||
|
|
||||||
|
const isValid = await isSessionValid({
|
||||||
|
serviceUrl,
|
||||||
|
session: selectedSession,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Session is valid:", isValid);
|
||||||
|
|
||||||
|
if (!isValid && selectedSession.factors?.user) {
|
||||||
|
// if the session is not valid anymore, we need to redirect the user to re-authenticate /
|
||||||
|
// TODO: handle IDP intent direcly if available
|
||||||
|
const command: SendLoginnameCommand = {
|
||||||
|
loginName: selectedSession.factors.user?.loginName,
|
||||||
|
organization: selectedSession.factors?.user?.organizationId,
|
||||||
|
requestId: `saml_${samlRequest}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await sendLoginname(command);
|
||||||
|
|
||||||
|
if (res && "redirect" in res && res?.redirect) {
|
||||||
|
const absoluteUrl = new URL(res.redirect, request.url);
|
||||||
|
return NextResponse.redirect(absoluteUrl.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookie = sessionCookies.find(
|
||||||
|
(cookie) => cookie.id === selectedSession?.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cookie && cookie.id && cookie.token) {
|
||||||
|
const session = {
|
||||||
|
sessionId: cookie?.id,
|
||||||
|
sessionToken: cookie?.token,
|
||||||
|
};
|
||||||
|
|
||||||
|
// works not with _rsc request
|
||||||
|
try {
|
||||||
|
const { url } = await createResponse({
|
||||||
|
serviceUrl,
|
||||||
|
req: create(CreateResponseRequestSchema, {
|
||||||
|
samlRequestId: samlRequest,
|
||||||
|
responseKind: {
|
||||||
|
case: "session",
|
||||||
|
value: session,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (url) {
|
||||||
|
return NextResponse.redirect(url);
|
||||||
|
} else {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "An error occurred!" },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
// handle already handled gracefully as these could come up if old emails with requestId are used (reset password, register emails etc.)
|
||||||
|
console.error(error);
|
||||||
|
if (
|
||||||
|
error &&
|
||||||
|
typeof error === "object" &&
|
||||||
|
"code" in error &&
|
||||||
|
error?.code === 9
|
||||||
|
) {
|
||||||
|
const loginSettings = await getLoginSettings({
|
||||||
|
serviceUrl,
|
||||||
|
organization: selectedSession.factors?.user?.organizationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loginSettings?.defaultRedirectUri) {
|
||||||
|
return NextResponse.redirect(loginSettings.defaultRedirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
const signedinUrl = new URL("/signedin", request.url);
|
||||||
|
|
||||||
|
if (selectedSession.factors?.user?.loginName) {
|
||||||
|
signedinUrl.searchParams.set(
|
||||||
|
"loginName",
|
||||||
|
selectedSession.factors?.user?.loginName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (selectedSession.factors?.user?.organizationId) {
|
||||||
|
signedinUrl.searchParams.set(
|
||||||
|
"organization",
|
||||||
|
selectedSession.factors?.user?.organizationId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return NextResponse.redirect(signedinUrl);
|
||||||
|
} else {
|
||||||
|
return NextResponse.json({ error }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -30,7 +30,7 @@ type CustomCookieData = {
|
|||||||
creationTs: string;
|
creationTs: string;
|
||||||
expirationTs: string;
|
expirationTs: string;
|
||||||
changeTs: string;
|
changeTs: string;
|
||||||
authRequestId?: string; // if its linked to an OIDC flow
|
requestId?: string; // if its linked to an OIDC flow
|
||||||
};
|
};
|
||||||
|
|
||||||
const passwordAttemptsHandler = (error: ConnectError) => {
|
const passwordAttemptsHandler = (error: ConnectError) => {
|
||||||
@@ -48,8 +48,7 @@ const passwordAttemptsHandler = (error: ConnectError) => {
|
|||||||
|
|
||||||
export async function createSessionAndUpdateCookie(
|
export async function createSessionAndUpdateCookie(
|
||||||
checks: Checks,
|
checks: Checks,
|
||||||
challenges: RequestChallenges | undefined,
|
requestId: string | undefined,
|
||||||
authRequestId: string | undefined,
|
|
||||||
lifetime?: Duration,
|
lifetime?: Duration,
|
||||||
): Promise<Session> {
|
): Promise<Session> {
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
@@ -57,9 +56,7 @@ export async function createSessionAndUpdateCookie(
|
|||||||
|
|
||||||
const createdSession = await createSessionFromChecks({
|
const createdSession = await createSessionFromChecks({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
checks,
|
checks,
|
||||||
challenges,
|
|
||||||
lifetime,
|
lifetime,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,8 +83,8 @@ export async function createSessionAndUpdateCookie(
|
|||||||
loginName: response.session.factors.user.loginName ?? "",
|
loginName: response.session.factors.user.loginName ?? "",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
sessionCookie.authRequestId = authRequestId;
|
sessionCookie.requestId = requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.session.factors.user.organizationId) {
|
if (response.session.factors.user.organizationId) {
|
||||||
@@ -113,7 +110,7 @@ export async function createSessionForIdpAndUpdateCookie(
|
|||||||
idpIntentId?: string | undefined;
|
idpIntentId?: string | undefined;
|
||||||
idpIntentToken?: string | undefined;
|
idpIntentToken?: string | undefined;
|
||||||
},
|
},
|
||||||
authRequestId: string | undefined,
|
requestId: string | undefined,
|
||||||
lifetime?: Duration,
|
lifetime?: Duration,
|
||||||
): Promise<Session> {
|
): Promise<Session> {
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
@@ -165,8 +162,8 @@ export async function createSessionForIdpAndUpdateCookie(
|
|||||||
organization: session.factors.user.organizationId ?? "",
|
organization: session.factors.user.organizationId ?? "",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
sessionCookie.authRequestId = authRequestId;
|
sessionCookie.requestId = requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.factors.user.organizationId) {
|
if (session.factors.user.organizationId) {
|
||||||
@@ -186,7 +183,7 @@ export async function setSessionAndUpdateCookie(
|
|||||||
recentCookie: CustomCookieData,
|
recentCookie: CustomCookieData,
|
||||||
checks?: Checks,
|
checks?: Checks,
|
||||||
challenges?: RequestChallenges,
|
challenges?: RequestChallenges,
|
||||||
authRequestId?: string,
|
requestId?: string,
|
||||||
lifetime?: Duration,
|
lifetime?: Duration,
|
||||||
) {
|
) {
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
@@ -216,8 +213,8 @@ export async function setSessionAndUpdateCookie(
|
|||||||
organization: recentCookie.organization,
|
organization: recentCookie.organization,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
sessionCookie.authRequestId = authRequestId;
|
sessionCookie.requestId = requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSession({
|
return getSession({
|
||||||
@@ -241,8 +238,8 @@ export async function setSessionAndUpdateCookie(
|
|||||||
organization: session.factors?.user?.organizationId ?? "",
|
organization: session.factors?.user?.organizationId ?? "",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (sessionCookie.authRequestId) {
|
if (sessionCookie.requestId) {
|
||||||
newCookie.authRequestId = sessionCookie.authRequestId;
|
newCookie.requestId = sessionCookie.requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
|
return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
|
||||||
|
@@ -55,7 +55,7 @@ type CreateNewSessionCommand = {
|
|||||||
loginName?: string;
|
loginName?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function createNewSessionFromIdpIntent(
|
export async function createNewSessionFromIdpIntent(
|
||||||
@@ -92,7 +92,7 @@ export async function createNewSessionFromIdpIntent(
|
|||||||
const session = await createSessionForIdpAndUpdateCookie(
|
const session = await createSessionForIdpAndUpdateCookie(
|
||||||
command.userId,
|
command.userId,
|
||||||
command.idpIntent,
|
command.idpIntent,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
loginSettings?.externalLoginCheckLifetime,
|
loginSettings?.externalLoginCheckLifetime,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ export async function createNewSessionFromIdpIntent(
|
|||||||
session,
|
session,
|
||||||
humanUser,
|
humanUser,
|
||||||
command.organization,
|
command.organization,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (emailVerificationCheck?.redirect) {
|
if (emailVerificationCheck?.redirect) {
|
||||||
@@ -118,16 +118,16 @@ export async function createNewSessionFromIdpIntent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check if user has MFA methods
|
// TODO: check if user has MFA methods
|
||||||
// const mfaFactorCheck = checkMFAFactors(session, loginSettings, authMethods, organization, authRequestId);
|
// const mfaFactorCheck = checkMFAFactors(session, loginSettings, authMethods, organization, requestId);
|
||||||
// if (mfaFactorCheck?.redirect) {
|
// if (mfaFactorCheck?.redirect) {
|
||||||
// return mfaFactorCheck;
|
// return mfaFactorCheck;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const url = await getNextUrl(
|
const url = await getNextUrl(
|
||||||
command.authRequestId && session.id
|
command.requestId && session.id
|
||||||
? {
|
? {
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
authRequestId: command.authRequestId,
|
requestId: command.requestId,
|
||||||
organization: session.factors.user.organizationId,
|
organization: session.factors.user.organizationId,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
@@ -11,7 +11,7 @@ type InviteUserCommand = {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RegisterUserResponse = {
|
export type RegisterUserResponse = {
|
||||||
|
@@ -25,7 +25,7 @@ import { createSessionAndUpdateCookie } from "./cookie";
|
|||||||
|
|
||||||
export type SendLoginnameCommand = {
|
export type SendLoginnameCommand = {
|
||||||
loginName: string;
|
loginName: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
};
|
};
|
||||||
@@ -96,8 +96,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
if (command.authRequestId) {
|
if (command.requestId) {
|
||||||
params.set("authRequestId", command.authRequestId);
|
params.set("requestId", command.requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.organization) {
|
if (command.organization) {
|
||||||
@@ -162,8 +162,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
|
|
||||||
const params = new URLSearchParams({ userId });
|
const params = new URLSearchParams({ userId });
|
||||||
|
|
||||||
if (command.authRequestId) {
|
if (command.requestId) {
|
||||||
params.set("authRequestId", command.authRequestId);
|
params.set("requestId", command.requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.organization) {
|
if (command.organization) {
|
||||||
@@ -243,8 +243,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
|
|
||||||
const session = await createSessionAndUpdateCookie(
|
const session = await createSessionAndUpdateCookie(
|
||||||
checks,
|
checks,
|
||||||
undefined,
|
command.requestId,
|
||||||
command.authRequestId,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!session.factors?.user?.id) {
|
if (!session.factors?.user?.id) {
|
||||||
@@ -269,7 +268,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
session,
|
session,
|
||||||
humanUser,
|
humanUser,
|
||||||
session.factors.user.organizationId,
|
session.factors.user.organizationId,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (inviteCheck?.redirect) {
|
if (inviteCheck?.redirect) {
|
||||||
@@ -288,8 +287,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.authRequestId) {
|
if (command.requestId) {
|
||||||
paramsAuthenticatorSetup.append("authRequestId", command.authRequestId);
|
paramsAuthenticatorSetup.append("requestId", command.requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { redirect: "/authenticator/set?" + paramsAuthenticatorSetup };
|
return { redirect: "/authenticator/set?" + paramsAuthenticatorSetup };
|
||||||
@@ -317,8 +316,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
command.organization ?? session.factors?.user?.organizationId;
|
command.organization ?? session.factors?.user?.organizationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.authRequestId) {
|
if (command.requestId) {
|
||||||
paramsPassword.authRequestId = command.authRequestId;
|
paramsPassword.requestId = command.requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -334,8 +333,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const paramsPasskey: any = { loginName: command.loginName };
|
const paramsPasskey: any = { loginName: command.loginName };
|
||||||
if (command.authRequestId) {
|
if (command.requestId) {
|
||||||
paramsPasskey.authRequestId = command.authRequestId;
|
paramsPasskey.requestId = command.requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.organization || session.factors?.user?.organizationId) {
|
if (command.organization || session.factors?.user?.organizationId) {
|
||||||
@@ -353,8 +352,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
altPassword: `${methods.authMethodTypes.includes(1)}`, // show alternative password option
|
altPassword: `${methods.authMethodTypes.includes(1)}`, // show alternative password option
|
||||||
};
|
};
|
||||||
|
|
||||||
if (command.authRequestId) {
|
if (command.requestId) {
|
||||||
passkeyParams.authRequestId = command.authRequestId;
|
passkeyParams.requestId = command.requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.organization || session.factors?.user?.organizationId) {
|
if (command.organization || session.factors?.user?.organizationId) {
|
||||||
@@ -373,8 +372,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
// user has no passkey setup and login settings allow passkeys
|
// user has no passkey setup and login settings allow passkeys
|
||||||
const paramsPasswordDefault: any = { loginName: command.loginName };
|
const paramsPasswordDefault: any = { loginName: command.loginName };
|
||||||
|
|
||||||
if (command.authRequestId) {
|
if (command.requestId) {
|
||||||
paramsPasswordDefault.authRequestId = command.authRequestId;
|
paramsPasswordDefault.requestId = command.requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.organization || session.factors?.user?.organizationId) {
|
if (command.organization || session.factors?.user?.organizationId) {
|
||||||
@@ -437,8 +436,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
if (orgToRegisterOn && !loginSettingsByContext?.ignoreUnknownUsernames) {
|
if (orgToRegisterOn && !loginSettingsByContext?.ignoreUnknownUsernames) {
|
||||||
const params = new URLSearchParams({ organization: orgToRegisterOn });
|
const params = new URLSearchParams({ organization: orgToRegisterOn });
|
||||||
|
|
||||||
if (command.authRequestId) {
|
if (command.requestId) {
|
||||||
params.set("authRequestId", command.authRequestId);
|
params.set("requestId", command.requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.loginName) {
|
if (command.loginName) {
|
||||||
@@ -454,8 +453,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
loginName: command.loginName,
|
loginName: command.loginName,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (command.authRequestId) {
|
if (command.requestId) {
|
||||||
paramsPasswordDefault.append("authRequestId", command.authRequestId);
|
paramsPasswordDefault.append("requestId", command.requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.organization) {
|
if (command.organization) {
|
||||||
|
@@ -20,7 +20,7 @@ export type SetOTPCommand = {
|
|||||||
loginName?: string;
|
loginName?: string;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
code: string;
|
code: string;
|
||||||
method: string;
|
method: string;
|
||||||
};
|
};
|
||||||
@@ -72,7 +72,7 @@ export async function setOTP(command: SetOTPCommand) {
|
|||||||
recentSession,
|
recentSession,
|
||||||
checks,
|
checks,
|
||||||
undefined,
|
undefined,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
loginSettings?.secondFactorCheckLifetime,
|
loginSettings?.secondFactorCheckLifetime,
|
||||||
).then((session) => {
|
).then((session) => {
|
||||||
return {
|
return {
|
||||||
|
@@ -139,12 +139,12 @@ type SendPasskeyCommand = {
|
|||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
checks?: Checks;
|
checks?: Checks;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
lifetime?: Duration;
|
lifetime?: Duration;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function sendPasskey(command: SendPasskeyCommand) {
|
export async function sendPasskey(command: SendPasskeyCommand) {
|
||||||
let { loginName, sessionId, organization, checks, authRequestId } = command;
|
let { loginName, sessionId, organization, checks, requestId } = command;
|
||||||
const recentSession = sessionId
|
const recentSession = sessionId
|
||||||
? await getSessionCookieById({ sessionId })
|
? await getSessionCookieById({ sessionId })
|
||||||
: loginName
|
: loginName
|
||||||
@@ -176,7 +176,7 @@ export async function sendPasskey(command: SendPasskeyCommand) {
|
|||||||
recentSession,
|
recentSession,
|
||||||
checks,
|
checks,
|
||||||
undefined,
|
undefined,
|
||||||
authRequestId,
|
requestId,
|
||||||
lifetime,
|
lifetime,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ export async function sendPasskey(command: SendPasskeyCommand) {
|
|||||||
session,
|
session,
|
||||||
humanUser,
|
humanUser,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (emailVerificationCheck?.redirect) {
|
if (emailVerificationCheck?.redirect) {
|
||||||
@@ -211,11 +211,11 @@ export async function sendPasskey(command: SendPasskeyCommand) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url =
|
const url =
|
||||||
authRequestId && session.id
|
requestId && session.id
|
||||||
? await getNextUrl(
|
? await getNextUrl(
|
||||||
{
|
{
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
authRequestId: authRequestId,
|
requestId: requestId,
|
||||||
organization: organization,
|
organization: organization,
|
||||||
},
|
},
|
||||||
loginSettings?.defaultRedirectUri,
|
loginSettings?.defaultRedirectUri,
|
||||||
|
@@ -16,7 +16,7 @@ import {
|
|||||||
setPassword,
|
setPassword,
|
||||||
setUserPassword,
|
setUserPassword,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { create } from "@zitadel/client";
|
import { ConnectError, create } from "@zitadel/client";
|
||||||
import { createServerTransport } from "@zitadel/client/node";
|
import { createServerTransport } from "@zitadel/client/node";
|
||||||
import { createUserServiceClient } from "@zitadel/client/v2";
|
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||||
import {
|
import {
|
||||||
@@ -42,7 +42,7 @@ import {
|
|||||||
type ResetPasswordCommand = {
|
type ResetPasswordCommand = {
|
||||||
loginName: string;
|
loginName: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function resetPassword(command: ResetPasswordCommand) {
|
export async function resetPassword(command: ResetPasswordCommand) {
|
||||||
@@ -77,7 +77,7 @@ export async function resetPassword(command: ResetPasswordCommand) {
|
|||||||
userId,
|
userId,
|
||||||
urlTemplate:
|
urlTemplate:
|
||||||
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` +
|
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` +
|
||||||
(command.authRequestId ? `&authRequestId=${command.authRequestId}` : ""),
|
(command.requestId ? `&requestId=${command.requestId}` : ""),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ export type UpdateSessionCommand = {
|
|||||||
loginName: string;
|
loginName: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
checks: Checks;
|
checks: Checks;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function sendPassword(command: UpdateSessionCommand) {
|
export async function sendPassword(command: UpdateSessionCommand) {
|
||||||
@@ -128,8 +128,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
try {
|
try {
|
||||||
session = await createSessionAndUpdateCookie(
|
session = await createSessionAndUpdateCookie(
|
||||||
checks,
|
checks,
|
||||||
undefined,
|
command.requestId,
|
||||||
command.authRequestId,
|
|
||||||
loginSettings?.passwordCheckLifetime,
|
loginSettings?.passwordCheckLifetime,
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -161,7 +160,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
sessionCookie,
|
sessionCookie,
|
||||||
command.checks,
|
command.checks,
|
||||||
undefined,
|
undefined,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
loginSettings?.passwordCheckLifetime,
|
loginSettings?.passwordCheckLifetime,
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -228,7 +227,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
session,
|
session,
|
||||||
humanUser,
|
humanUser,
|
||||||
command.organization,
|
command.organization,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (passwordChangedCheck?.redirect) {
|
if (passwordChangedCheck?.redirect) {
|
||||||
@@ -245,7 +244,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
session,
|
session,
|
||||||
humanUser,
|
humanUser,
|
||||||
command.organization,
|
command.organization,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (emailVerificationCheck?.redirect) {
|
if (emailVerificationCheck?.redirect) {
|
||||||
@@ -269,23 +268,24 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
return { error: "Could not verify password!" };
|
return { error: "Could not verify password!" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const mfaFactorCheck = checkMFAFactors(
|
const mfaFactorCheck = await checkMFAFactors(
|
||||||
|
serviceUrl,
|
||||||
session,
|
session,
|
||||||
loginSettings,
|
loginSettings,
|
||||||
authMethods,
|
authMethods,
|
||||||
command.organization,
|
command.organization,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mfaFactorCheck?.redirect) {
|
if (mfaFactorCheck?.redirect) {
|
||||||
return mfaFactorCheck;
|
return mfaFactorCheck;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.authRequestId && session.id) {
|
if (command.requestId && session.id) {
|
||||||
const nextUrl = await getNextUrl(
|
const nextUrl = await getNextUrl(
|
||||||
{
|
{
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
authRequestId: command.authRequestId,
|
requestId: command.requestId,
|
||||||
organization:
|
organization:
|
||||||
command.organization ?? session.factors?.user?.organizationId,
|
command.organization ?? session.factors?.user?.organizationId,
|
||||||
},
|
},
|
||||||
@@ -435,7 +435,7 @@ export async function checkSessionAndSetPassword({
|
|||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
.catch((error) => {
|
.catch((error: ConnectError) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
if (error.code === 7) {
|
if (error.code === 7) {
|
||||||
return { error: "Session is not valid." };
|
return { error: "Session is not valid." };
|
||||||
|
@@ -19,7 +19,7 @@ type RegisterUserCommand = {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RegisterUserResponse = {
|
export type RegisterUserResponse = {
|
||||||
@@ -71,8 +71,7 @@ export async function registerUser(command: RegisterUserCommand) {
|
|||||||
|
|
||||||
const session = await createSessionAndUpdateCookie(
|
const session = await createSessionAndUpdateCookie(
|
||||||
checks,
|
checks,
|
||||||
undefined,
|
command.requestId,
|
||||||
command.authRequestId,
|
|
||||||
command.password ? loginSettings?.passwordCheckLifetime : undefined,
|
command.password ? loginSettings?.passwordCheckLifetime : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -86,8 +85,8 @@ export async function registerUser(command: RegisterUserCommand) {
|
|||||||
organization: session.factors.user.organizationId,
|
organization: session.factors.user.organizationId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (command.authRequestId) {
|
if (command.requestId) {
|
||||||
params.append("authRequestId", command.authRequestId);
|
params.append("requestId", command.requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { redirect: "/passkey/set?" + params };
|
return { redirect: "/passkey/set?" + params };
|
||||||
@@ -111,7 +110,7 @@ export async function registerUser(command: RegisterUserCommand) {
|
|||||||
session,
|
session,
|
||||||
humanUser,
|
humanUser,
|
||||||
session.factors.user.organizationId,
|
session.factors.user.organizationId,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (emailVerificationCheck?.redirect) {
|
if (emailVerificationCheck?.redirect) {
|
||||||
@@ -119,10 +118,10 @@ export async function registerUser(command: RegisterUserCommand) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = await getNextUrl(
|
const url = await getNextUrl(
|
||||||
command.authRequestId && session.id
|
command.requestId && session.id
|
||||||
? {
|
? {
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
authRequestId: command.authRequestId,
|
requestId: command.requestId,
|
||||||
organization: session.factors.user.organizationId,
|
organization: session.factors.user.organizationId,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
@@ -4,6 +4,7 @@ import { setSessionAndUpdateCookie } from "@/lib/server/cookie";
|
|||||||
import {
|
import {
|
||||||
deleteSession,
|
deleteSession,
|
||||||
getLoginSettings,
|
getLoginSettings,
|
||||||
|
humanMFAInitSkipped,
|
||||||
listAuthenticationMethodTypes,
|
listAuthenticationMethodTypes,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { Duration } from "@zitadel/client";
|
import { Duration } from "@zitadel/client";
|
||||||
@@ -20,10 +21,57 @@ import {
|
|||||||
} from "../cookies";
|
} from "../cookies";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service";
|
||||||
|
|
||||||
|
export async function skipMFAAndContinueWithNextUrl({
|
||||||
|
userId,
|
||||||
|
requestId,
|
||||||
|
loginName,
|
||||||
|
sessionId,
|
||||||
|
organization,
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
loginName?: string;
|
||||||
|
sessionId?: string;
|
||||||
|
requestId?: string;
|
||||||
|
organization?: string;
|
||||||
|
}) {
|
||||||
|
const _headers = await headers();
|
||||||
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
|
const loginSettings = await getLoginSettings({
|
||||||
|
serviceUrl,
|
||||||
|
organization: organization,
|
||||||
|
});
|
||||||
|
|
||||||
|
await humanMFAInitSkipped({ serviceUrl, userId });
|
||||||
|
|
||||||
|
const url =
|
||||||
|
requestId && sessionId
|
||||||
|
? await getNextUrl(
|
||||||
|
{
|
||||||
|
sessionId: sessionId,
|
||||||
|
requestId: requestId,
|
||||||
|
organization: organization,
|
||||||
|
},
|
||||||
|
loginSettings?.defaultRedirectUri,
|
||||||
|
)
|
||||||
|
: loginName
|
||||||
|
? await getNextUrl(
|
||||||
|
{
|
||||||
|
loginName: loginName,
|
||||||
|
organization: organization,
|
||||||
|
},
|
||||||
|
loginSettings?.defaultRedirectUri,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
if (url) {
|
||||||
|
return { redirect: url };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function continueWithSession({
|
export async function continueWithSession({
|
||||||
authRequestId,
|
requestId,
|
||||||
...session
|
...session
|
||||||
}: Session & { authRequestId?: string }) {
|
}: Session & { requestId?: string }) {
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
@@ -34,11 +82,11 @@ export async function continueWithSession({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const url =
|
const url =
|
||||||
authRequestId && session.id && session.factors?.user
|
requestId && session.id && session.factors?.user
|
||||||
? await getNextUrl(
|
? await getNextUrl(
|
||||||
{
|
{
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
authRequestId: authRequestId,
|
requestId: requestId,
|
||||||
organization: session.factors.user.organizationId,
|
organization: session.factors.user.organizationId,
|
||||||
},
|
},
|
||||||
loginSettings?.defaultRedirectUri,
|
loginSettings?.defaultRedirectUri,
|
||||||
@@ -62,20 +110,14 @@ export type UpdateSessionCommand = {
|
|||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
checks?: Checks;
|
checks?: Checks;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
challenges?: RequestChallenges;
|
challenges?: RequestChallenges;
|
||||||
lifetime?: Duration;
|
lifetime?: Duration;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function updateSession(options: UpdateSessionCommand) {
|
export async function updateSession(options: UpdateSessionCommand) {
|
||||||
let {
|
let { loginName, sessionId, organization, checks, requestId, challenges } =
|
||||||
loginName,
|
options;
|
||||||
sessionId,
|
|
||||||
organization,
|
|
||||||
checks,
|
|
||||||
authRequestId,
|
|
||||||
challenges,
|
|
||||||
} = options;
|
|
||||||
const recentSession = sessionId
|
const recentSession = sessionId
|
||||||
? await getSessionCookieById({ sessionId })
|
? await getSessionCookieById({ sessionId })
|
||||||
: loginName
|
: loginName
|
||||||
@@ -123,7 +165,7 @@ export async function updateSession(options: UpdateSessionCommand) {
|
|||||||
recentSession,
|
recentSession,
|
||||||
checks,
|
checks,
|
||||||
challenges,
|
challenges,
|
||||||
authRequestId,
|
requestId,
|
||||||
lifetime,
|
lifetime,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -59,7 +59,7 @@ type VerifyUserByEmailCommand = {
|
|||||||
organization?: string;
|
organization?: string;
|
||||||
code: string;
|
code: string;
|
||||||
isInvite: boolean;
|
isInvite: boolean;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function sendVerification(command: VerifyUserByEmailCommand) {
|
export async function sendVerification(command: VerifyUserByEmailCommand) {
|
||||||
@@ -155,11 +155,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
session = await createSessionAndUpdateCookie(
|
session = await createSessionAndUpdateCookie(checks, command.requestId);
|
||||||
checks,
|
|
||||||
undefined,
|
|
||||||
command.authRequestId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session?.factors?.user?.id) {
|
if (!session?.factors?.user?.id) {
|
||||||
@@ -207,12 +203,13 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// redirect to mfa factor if user has one, or redirect to set one up
|
// redirect to mfa factor if user has one, or redirect to set one up
|
||||||
const mfaFactorCheck = checkMFAFactors(
|
const mfaFactorCheck = await checkMFAFactors(
|
||||||
|
serviceUrl,
|
||||||
session,
|
session,
|
||||||
loginSettings,
|
loginSettings,
|
||||||
authMethodResponse.authMethodTypes,
|
authMethodResponse.authMethodTypes,
|
||||||
command.organization,
|
command.organization,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mfaFactorCheck?.redirect) {
|
if (mfaFactorCheck?.redirect) {
|
||||||
@@ -220,11 +217,11 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// login user if no additional steps are required
|
// login user if no additional steps are required
|
||||||
if (command.authRequestId && session.id) {
|
if (command.requestId && session.id) {
|
||||||
const nextUrl = await getNextUrl(
|
const nextUrl = await getNextUrl(
|
||||||
{
|
{
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
authRequestId: command.authRequestId,
|
requestId: command.requestId,
|
||||||
organization:
|
organization:
|
||||||
command.organization ?? session.factors?.user?.organizationId,
|
command.organization ?? session.factors?.user?.organizationId,
|
||||||
},
|
},
|
||||||
@@ -248,7 +245,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
|
|||||||
type resendVerifyEmailCommand = {
|
type resendVerifyEmailCommand = {
|
||||||
userId: string;
|
userId: string;
|
||||||
isInvite: boolean;
|
isInvite: boolean;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function resendVerification(command: resendVerifyEmailCommand) {
|
export async function resendVerification(command: resendVerifyEmailCommand) {
|
||||||
@@ -269,9 +266,7 @@ export async function resendVerification(command: resendVerifyEmailCommand) {
|
|||||||
serviceUrl,
|
serviceUrl,
|
||||||
urlTemplate:
|
urlTemplate:
|
||||||
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` +
|
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` +
|
||||||
(command.authRequestId
|
(command.requestId ? `&requestId=${command.requestId}` : ""),
|
||||||
? `&authRequestId=${command.authRequestId}`
|
|
||||||
: ""),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +287,7 @@ export async function sendEmailCode(command: sendEmailCommand) {
|
|||||||
|
|
||||||
export type SendVerificationRedirectWithoutCheckCommand = {
|
export type SendVerificationRedirectWithoutCheckCommand = {
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
requestId?: string;
|
||||||
} & (
|
} & (
|
||||||
| { userId: string; loginName?: never }
|
| { userId: string; loginName?: never }
|
||||||
| { userId?: never; loginName: string }
|
| { userId?: never; loginName: string }
|
||||||
@@ -371,11 +366,7 @@ export async function sendVerificationRedirectWithoutCheck(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
session = await createSessionAndUpdateCookie(
|
session = await createSessionAndUpdateCookie(checks, command.requestId);
|
||||||
checks,
|
|
||||||
undefined,
|
|
||||||
command.authRequestId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session?.factors?.user?.id) {
|
if (!session?.factors?.user?.id) {
|
||||||
@@ -418,17 +409,17 @@ export async function sendVerificationRedirectWithoutCheck(
|
|||||||
|
|
||||||
const loginSettings = await getLoginSettings({
|
const loginSettings = await getLoginSettings({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
organization: user.details?.resourceOwner,
|
organization: user.details?.resourceOwner,
|
||||||
});
|
});
|
||||||
|
|
||||||
// redirect to mfa factor if user has one, or redirect to set one up
|
// redirect to mfa factor if user has one, or redirect to set one up
|
||||||
const mfaFactorCheck = checkMFAFactors(
|
const mfaFactorCheck = await checkMFAFactors(
|
||||||
|
serviceUrl,
|
||||||
session,
|
session,
|
||||||
loginSettings,
|
loginSettings,
|
||||||
authMethodResponse.authMethodTypes,
|
authMethodResponse.authMethodTypes,
|
||||||
command.organization,
|
command.organization,
|
||||||
command.authRequestId,
|
command.requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mfaFactorCheck?.redirect) {
|
if (mfaFactorCheck?.redirect) {
|
||||||
@@ -436,11 +427,11 @@ export async function sendVerificationRedirectWithoutCheck(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// login user if no additional steps are required
|
// login user if no additional steps are required
|
||||||
if (command.authRequestId && session.id) {
|
if (command.requestId && session.id) {
|
||||||
const nextUrl = await getNextUrl(
|
const nextUrl = await getNextUrl(
|
||||||
{
|
{
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
authRequestId: command.authRequestId,
|
requestId: command.requestId,
|
||||||
organization:
|
organization:
|
||||||
command.organization ?? session.factors?.user?.organizationId,
|
command.organization ?? session.factors?.user?.organizationId,
|
||||||
},
|
},
|
||||||
|
@@ -3,6 +3,7 @@ import { createServerTransport } from "@zitadel/client/node";
|
|||||||
import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb";
|
import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb";
|
||||||
import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||||
import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb";
|
import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb";
|
||||||
|
import { SAMLService } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb";
|
||||||
import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||||
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
||||||
import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
@@ -15,7 +16,8 @@ type ServiceClass =
|
|||||||
| typeof OrganizationService
|
| typeof OrganizationService
|
||||||
| typeof SessionService
|
| typeof SessionService
|
||||||
| typeof OIDCService
|
| typeof OIDCService
|
||||||
| typeof SettingsService;
|
| typeof SettingsService
|
||||||
|
| typeof SAMLService;
|
||||||
|
|
||||||
export async function createServiceForHost<T extends ServiceClass>(
|
export async function createServiceForHost<T extends ServiceClass>(
|
||||||
service: T,
|
service: T,
|
||||||
|
@@ -1,7 +1,15 @@
|
|||||||
|
import { timestampDate } from "@zitadel/client";
|
||||||
|
import { AuthRequest } from "@zitadel/proto/zitadel/oidc/v2/authorization_pb";
|
||||||
|
import { SAMLRequest } from "@zitadel/proto/zitadel/saml/v2/authorization_pb";
|
||||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
import { GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
import { GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||||
|
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
import { getMostRecentCookieWithLoginname } from "./cookies";
|
import { getMostRecentCookieWithLoginname } from "./cookies";
|
||||||
import { getSession } from "./zitadel";
|
import {
|
||||||
|
getLoginSettings,
|
||||||
|
getSession,
|
||||||
|
listAuthenticationMethodTypes,
|
||||||
|
} from "./zitadel";
|
||||||
|
|
||||||
type LoadMostRecentSessionParams = {
|
type LoadMostRecentSessionParams = {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
@@ -29,3 +37,160 @@ export async function loadMostRecentSession({
|
|||||||
sessionToken: recent.token,
|
sessionToken: recent.token,
|
||||||
}).then((resp: GetSessionResponse) => resp.session);
|
}).then((resp: GetSessionResponse) => resp.session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mfa is required, session is not valid anymore (e.g. session expired, user logged out, etc.)
|
||||||
|
* to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId);
|
||||||
|
**/
|
||||||
|
export async function isSessionValid({
|
||||||
|
serviceUrl,
|
||||||
|
session,
|
||||||
|
}: {
|
||||||
|
serviceUrl: string;
|
||||||
|
session: Session;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
// session can't be checked without user
|
||||||
|
if (!session.factors?.user) {
|
||||||
|
console.warn("Session has no user");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mfaValid = true;
|
||||||
|
|
||||||
|
const authMethodTypes = await listAuthenticationMethodTypes({
|
||||||
|
serviceUrl,
|
||||||
|
userId: session.factors.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const authMethods = authMethodTypes.authMethodTypes;
|
||||||
|
if (authMethods && authMethods.includes(AuthenticationMethodType.TOTP)) {
|
||||||
|
mfaValid = !!session.factors.totp?.verifiedAt;
|
||||||
|
if (!mfaValid) {
|
||||||
|
console.warn(
|
||||||
|
"Session has no valid totpEmail factor",
|
||||||
|
session.factors.totp?.verifiedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
authMethods &&
|
||||||
|
authMethods.includes(AuthenticationMethodType.OTP_EMAIL)
|
||||||
|
) {
|
||||||
|
mfaValid = !!session.factors.otpEmail?.verifiedAt;
|
||||||
|
if (!mfaValid) {
|
||||||
|
console.warn(
|
||||||
|
"Session has no valid otpEmail factor",
|
||||||
|
session.factors.otpEmail?.verifiedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
authMethods &&
|
||||||
|
authMethods.includes(AuthenticationMethodType.OTP_SMS)
|
||||||
|
) {
|
||||||
|
mfaValid = !!session.factors.otpSms?.verifiedAt;
|
||||||
|
if (!mfaValid) {
|
||||||
|
console.warn(
|
||||||
|
"Session has no valid otpSms factor",
|
||||||
|
session.factors.otpSms?.verifiedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
authMethods &&
|
||||||
|
authMethods.includes(AuthenticationMethodType.U2F)
|
||||||
|
) {
|
||||||
|
mfaValid = !!session.factors.webAuthN?.verifiedAt;
|
||||||
|
if (!mfaValid) {
|
||||||
|
console.warn(
|
||||||
|
"Session has no valid u2f factor",
|
||||||
|
session.factors.webAuthN?.verifiedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// only check settings if no auth methods are available, as this would require a setup
|
||||||
|
const loginSettings = await getLoginSettings({
|
||||||
|
serviceUrl,
|
||||||
|
organization: session.factors?.user?.organizationId,
|
||||||
|
});
|
||||||
|
if (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) {
|
||||||
|
const otpEmail = session.factors.otpEmail?.verifiedAt;
|
||||||
|
const otpSms = session.factors.otpSms?.verifiedAt;
|
||||||
|
const totp = session.factors.totp?.verifiedAt;
|
||||||
|
const webAuthN = session.factors.webAuthN?.verifiedAt;
|
||||||
|
const idp = session.factors.intent?.verifiedAt; // TODO: forceMFA should not consider this as valid factor
|
||||||
|
|
||||||
|
// must have one single check
|
||||||
|
mfaValid = !!(otpEmail || otpSms || totp || webAuthN || idp);
|
||||||
|
if (!mfaValid) {
|
||||||
|
console.warn("Session has no valid multifactor", session.factors);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mfaValid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validPassword = session?.factors?.password?.verifiedAt;
|
||||||
|
const validPasskey = session?.factors?.webAuthN?.verifiedAt;
|
||||||
|
const validIDP = session?.factors?.intent?.verifiedAt;
|
||||||
|
|
||||||
|
const stillValid = session.expirationDate
|
||||||
|
? timestampDate(session.expirationDate).getTime() > new Date().getTime()
|
||||||
|
: true;
|
||||||
|
|
||||||
|
if (!stillValid) {
|
||||||
|
console.warn(
|
||||||
|
"Session is expired",
|
||||||
|
session.expirationDate
|
||||||
|
? timestampDate(session.expirationDate).toDateString()
|
||||||
|
: "no expiration date",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validChecks = !!(validPassword || validPasskey || validIDP);
|
||||||
|
|
||||||
|
return stillValid && validChecks && mfaValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findValidSession({
|
||||||
|
serviceUrl,
|
||||||
|
sessions,
|
||||||
|
authRequest,
|
||||||
|
samlRequest,
|
||||||
|
}: {
|
||||||
|
serviceUrl: string;
|
||||||
|
sessions: Session[];
|
||||||
|
authRequest?: AuthRequest;
|
||||||
|
samlRequest?: SAMLRequest;
|
||||||
|
}): Promise<Session | undefined> {
|
||||||
|
const sessionsWithHint = sessions.filter((s) => {
|
||||||
|
if (authRequest && authRequest.hintUserId) {
|
||||||
|
return s.factors?.user?.id === authRequest.hintUserId;
|
||||||
|
}
|
||||||
|
if (authRequest && authRequest.loginHint) {
|
||||||
|
return s.factors?.user?.loginName === authRequest.loginHint;
|
||||||
|
}
|
||||||
|
if (samlRequest) {
|
||||||
|
// TODO: do whatever
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sessionsWithHint.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by change date descending
|
||||||
|
sessionsWithHint.sort((a, b) => {
|
||||||
|
const dateA = a.changeDate ? timestampDate(a.changeDate).getTime() : 0;
|
||||||
|
const dateB = b.changeDate ? timestampDate(b.changeDate).getTime() : 0;
|
||||||
|
return dateB - dateA;
|
||||||
|
});
|
||||||
|
|
||||||
|
// return the first valid session according to settings
|
||||||
|
for (const session of sessionsWithHint) {
|
||||||
|
if (await isSessionValid({ serviceUrl, session })) {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
@@ -5,13 +5,14 @@ import { PasswordExpirySettings } from "@zitadel/proto/zitadel/settings/v2/passw
|
|||||||
import { HumanUser } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
import { HumanUser } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import { getUserByID } from "./zitadel";
|
||||||
|
|
||||||
export function checkPasswordChangeRequired(
|
export function checkPasswordChangeRequired(
|
||||||
expirySettings: PasswordExpirySettings | undefined,
|
expirySettings: PasswordExpirySettings | undefined,
|
||||||
session: Session,
|
session: Session,
|
||||||
humanUser: HumanUser | undefined,
|
humanUser: HumanUser | undefined,
|
||||||
organization?: string,
|
organization?: string,
|
||||||
authRequestId?: string,
|
requestId?: string,
|
||||||
) {
|
) {
|
||||||
let isOutdated = false;
|
let isOutdated = false;
|
||||||
if (expirySettings?.maxAgeDays && humanUser?.passwordChanged) {
|
if (expirySettings?.maxAgeDays && humanUser?.passwordChanged) {
|
||||||
@@ -35,8 +36,8 @@ export function checkPasswordChangeRequired(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.append("authRequestId", authRequestId);
|
params.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { redirect: "/password/change?" + params };
|
return { redirect: "/password/change?" + params };
|
||||||
@@ -47,7 +48,7 @@ export function checkInvite(
|
|||||||
session: Session,
|
session: Session,
|
||||||
humanUser?: HumanUser,
|
humanUser?: HumanUser,
|
||||||
organization?: string,
|
organization?: string,
|
||||||
authRequestId?: string,
|
requestId?: string,
|
||||||
) {
|
) {
|
||||||
if (!humanUser?.email?.isVerified) {
|
if (!humanUser?.email?.isVerified) {
|
||||||
const paramsVerify = new URLSearchParams({
|
const paramsVerify = new URLSearchParams({
|
||||||
@@ -63,8 +64,8 @@ export function checkInvite(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
paramsVerify.append("authRequestId", authRequestId);
|
paramsVerify.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { redirect: "/verify?" + paramsVerify };
|
return { redirect: "/verify?" + paramsVerify };
|
||||||
@@ -75,7 +76,7 @@ export function checkEmailVerification(
|
|||||||
session: Session,
|
session: Session,
|
||||||
humanUser?: HumanUser,
|
humanUser?: HumanUser,
|
||||||
organization?: string,
|
organization?: string,
|
||||||
authRequestId?: string,
|
requestId?: string,
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
!humanUser?.email?.isVerified &&
|
!humanUser?.email?.isVerified &&
|
||||||
@@ -85,8 +86,8 @@ export function checkEmailVerification(
|
|||||||
loginName: session.factors?.user?.loginName as string,
|
loginName: session.factors?.user?.loginName as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.append("authRequestId", authRequestId);
|
params.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization || session.factors?.user?.organizationId) {
|
if (organization || session.factors?.user?.organizationId) {
|
||||||
@@ -100,12 +101,13 @@ export function checkEmailVerification(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkMFAFactors(
|
export async function checkMFAFactors(
|
||||||
|
serviceUrl: string,
|
||||||
session: Session,
|
session: Session,
|
||||||
loginSettings: LoginSettings | undefined,
|
loginSettings: LoginSettings | undefined,
|
||||||
authMethods: AuthenticationMethodType[],
|
authMethods: AuthenticationMethodType[],
|
||||||
organization?: string,
|
organization?: string,
|
||||||
authRequestId?: string,
|
requestId?: string,
|
||||||
) {
|
) {
|
||||||
const availableMultiFactors = authMethods?.filter(
|
const availableMultiFactors = authMethods?.filter(
|
||||||
(m: AuthenticationMethodType) =>
|
(m: AuthenticationMethodType) =>
|
||||||
@@ -128,8 +130,8 @@ export function checkMFAFactors(
|
|||||||
loginName: session.factors?.user?.loginName as string,
|
loginName: session.factors?.user?.loginName as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.append("authRequestId", authRequestId);
|
params.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization || session.factors?.user?.organizationId) {
|
if (organization || session.factors?.user?.organizationId) {
|
||||||
@@ -155,8 +157,8 @@ export function checkMFAFactors(
|
|||||||
loginName: session.factors?.user?.loginName as string,
|
loginName: session.factors?.user?.loginName as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.append("authRequestId", authRequestId);
|
params.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization || session.factors?.user?.organizationId) {
|
if (organization || session.factors?.user?.organizationId) {
|
||||||
@@ -177,8 +179,62 @@ export function checkMFAFactors(
|
|||||||
checkAfter: "true", // this defines if the check is directly made after the setup
|
checkAfter: "true", // this defines if the check is directly made after the setup
|
||||||
});
|
});
|
||||||
|
|
||||||
if (authRequestId) {
|
if (requestId) {
|
||||||
params.append("authRequestId", authRequestId);
|
params.append("requestId", requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (organization || session.factors?.user?.organizationId) {
|
||||||
|
params.append(
|
||||||
|
"organization",
|
||||||
|
organization ?? (session.factors?.user?.organizationId as string),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: provide a way to setup passkeys on mfa page?
|
||||||
|
return { redirect: `/mfa/set?` + params };
|
||||||
|
} else if (
|
||||||
|
loginSettings?.mfaInitSkipLifetime &&
|
||||||
|
(loginSettings.mfaInitSkipLifetime.nanos > 0 ||
|
||||||
|
loginSettings.mfaInitSkipLifetime.seconds > 0) &&
|
||||||
|
!availableMultiFactors.length &&
|
||||||
|
session?.factors?.user?.id
|
||||||
|
) {
|
||||||
|
const userResponse = await getUserByID({
|
||||||
|
serviceUrl,
|
||||||
|
userId: session.factors?.user?.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const humanUser =
|
||||||
|
userResponse?.user?.type.case === "human"
|
||||||
|
? userResponse?.user.type.value
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (humanUser?.mfaInitSkipped) {
|
||||||
|
const mfaInitSkippedTimestamp = timestampDate(humanUser.mfaInitSkipped);
|
||||||
|
|
||||||
|
const mfaInitSkipLifetimeMillis =
|
||||||
|
Number(loginSettings.mfaInitSkipLifetime.seconds) * 1000 +
|
||||||
|
loginSettings.mfaInitSkipLifetime.nanos / 1000000;
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const mfaInitSkippedTime = mfaInitSkippedTimestamp.getTime();
|
||||||
|
const timeDifference = currentTime - mfaInitSkippedTime;
|
||||||
|
|
||||||
|
if (!(timeDifference > mfaInitSkipLifetimeMillis)) {
|
||||||
|
// if the time difference is smaller than the lifetime, skip the mfa setup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the user has never skipped the mfa init but we have a setting so we redirect
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
loginName: session.factors?.user?.loginName as string,
|
||||||
|
force: "false", // this defines if the mfa is not forced in the settings and can be skipped
|
||||||
|
checkAfter: "true", // this defines if the check is directly made after the setup
|
||||||
|
});
|
||||||
|
|
||||||
|
if (requestId) {
|
||||||
|
params.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization || session.factors?.user?.organizationId) {
|
if (organization || session.factors?.user?.organizationId) {
|
||||||
@@ -191,28 +247,4 @@ export function checkMFAFactors(
|
|||||||
// TODO: provide a way to setup passkeys on mfa page?
|
// TODO: provide a way to setup passkeys on mfa page?
|
||||||
return { redirect: `/mfa/set?` + params };
|
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);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,10 @@ import {
|
|||||||
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||||
import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb";
|
import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb";
|
||||||
|
import {
|
||||||
|
CreateResponseRequest,
|
||||||
|
SAMLService,
|
||||||
|
} from "@zitadel/proto/zitadel/saml/v2/saml_service_pb";
|
||||||
import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||||
import {
|
import {
|
||||||
Checks,
|
Checks,
|
||||||
@@ -55,11 +59,9 @@ async function cacheWrapper<T>(callback: Promise<T>) {
|
|||||||
|
|
||||||
export async function getBrandingSettings({
|
export async function getBrandingSettings({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
organization,
|
organization,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
organization?: string;
|
organization?: string;
|
||||||
}) {
|
}) {
|
||||||
const settingsService: Client<typeof SettingsService> =
|
const settingsService: Client<typeof SettingsService> =
|
||||||
@@ -74,11 +76,9 @@ export async function getBrandingSettings({
|
|||||||
|
|
||||||
export async function getLoginSettings({
|
export async function getLoginSettings({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
organization,
|
organization,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
organization?: string;
|
organization?: string;
|
||||||
}) {
|
}) {
|
||||||
const settingsService: Client<typeof SettingsService> =
|
const settingsService: Client<typeof SettingsService> =
|
||||||
@@ -93,11 +93,9 @@ export async function getLoginSettings({
|
|||||||
|
|
||||||
export async function getLockoutSettings({
|
export async function getLockoutSettings({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
orgId,
|
orgId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
}) {
|
}) {
|
||||||
const settingsService: Client<typeof SettingsService> =
|
const settingsService: Client<typeof SettingsService> =
|
||||||
@@ -112,11 +110,9 @@ export async function getLockoutSettings({
|
|||||||
|
|
||||||
export async function getPasswordExpirySettings({
|
export async function getPasswordExpirySettings({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
orgId,
|
orgId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
}) {
|
}) {
|
||||||
const settingsService: Client<typeof SettingsService> =
|
const settingsService: Client<typeof SettingsService> =
|
||||||
@@ -131,11 +127,9 @@ export async function getPasswordExpirySettings({
|
|||||||
|
|
||||||
export async function listIDPLinks({
|
export async function listIDPLinks({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -148,11 +142,9 @@ export async function listIDPLinks({
|
|||||||
|
|
||||||
export async function addOTPEmail({
|
export async function addOTPEmail({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -165,11 +157,9 @@ export async function addOTPEmail({
|
|||||||
|
|
||||||
export async function addOTPSMS({
|
export async function addOTPSMS({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -182,11 +172,9 @@ export async function addOTPSMS({
|
|||||||
|
|
||||||
export async function registerTOTP({
|
export async function registerTOTP({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -214,11 +202,9 @@ export async function getGeneralSettings({
|
|||||||
|
|
||||||
export async function getLegalAndSupportSettings({
|
export async function getLegalAndSupportSettings({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
organization,
|
organization,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
organization?: string;
|
organization?: string;
|
||||||
}) {
|
}) {
|
||||||
const settingsService: Client<typeof SettingsService> =
|
const settingsService: Client<typeof SettingsService> =
|
||||||
@@ -233,11 +219,9 @@ export async function getLegalAndSupportSettings({
|
|||||||
|
|
||||||
export async function getPasswordComplexitySettings({
|
export async function getPasswordComplexitySettings({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
organization,
|
organization,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
organization?: string;
|
organization?: string;
|
||||||
}) {
|
}) {
|
||||||
const settingsService: Client<typeof SettingsService> =
|
const settingsService: Client<typeof SettingsService> =
|
||||||
@@ -252,32 +236,26 @@ export async function getPasswordComplexitySettings({
|
|||||||
|
|
||||||
export async function createSessionFromChecks({
|
export async function createSessionFromChecks({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
checks,
|
checks,
|
||||||
challenges,
|
|
||||||
lifetime,
|
lifetime,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
checks: Checks;
|
checks: Checks;
|
||||||
challenges: RequestChallenges | undefined;
|
|
||||||
lifetime?: Duration;
|
lifetime?: Duration;
|
||||||
}) {
|
}) {
|
||||||
const sessionService: Client<typeof SessionService> =
|
const sessionService: Client<typeof SessionService> =
|
||||||
await createServiceForHost(SessionService, serviceUrl);
|
await createServiceForHost(SessionService, serviceUrl);
|
||||||
|
|
||||||
return sessionService.createSession({ checks, challenges, lifetime }, {});
|
return sessionService.createSession({ checks, lifetime }, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createSessionForUserIdAndIdpIntent({
|
export async function createSessionForUserIdAndIdpIntent({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
idpIntent,
|
idpIntent,
|
||||||
lifetime,
|
lifetime,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
idpIntent: {
|
idpIntent: {
|
||||||
idpIntentId?: string | undefined;
|
idpIntentId?: string | undefined;
|
||||||
@@ -304,7 +282,6 @@ export async function createSessionForUserIdAndIdpIntent({
|
|||||||
|
|
||||||
export async function setSession({
|
export async function setSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
sessionId,
|
sessionId,
|
||||||
sessionToken,
|
sessionToken,
|
||||||
challenges,
|
challenges,
|
||||||
@@ -312,7 +289,6 @@ export async function setSession({
|
|||||||
lifetime,
|
lifetime,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
sessionToken: string;
|
sessionToken: string;
|
||||||
challenges: RequestChallenges | undefined;
|
challenges: RequestChallenges | undefined;
|
||||||
@@ -337,12 +313,10 @@ export async function setSession({
|
|||||||
|
|
||||||
export async function getSession({
|
export async function getSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
sessionId,
|
sessionId,
|
||||||
sessionToken,
|
sessionToken,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
sessionToken: string;
|
sessionToken: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -354,12 +328,10 @@ export async function getSession({
|
|||||||
|
|
||||||
export async function deleteSession({
|
export async function deleteSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
sessionId,
|
sessionId,
|
||||||
sessionToken,
|
sessionToken,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
sessionToken: string;
|
sessionToken: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -371,7 +343,6 @@ export async function deleteSession({
|
|||||||
|
|
||||||
type ListSessionsCommand = {
|
type ListSessionsCommand = {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
ids: string[];
|
ids: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -400,7 +371,6 @@ export async function listSessions({
|
|||||||
|
|
||||||
export type AddHumanUserData = {
|
export type AddHumanUserData = {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
email: string;
|
email: string;
|
||||||
@@ -410,7 +380,6 @@ export type AddHumanUserData = {
|
|||||||
|
|
||||||
export async function addHumanUser({
|
export async function addHumanUser({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
@@ -443,11 +412,9 @@ export async function addHumanUser({
|
|||||||
|
|
||||||
export async function addHuman({
|
export async function addHuman({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
request,
|
request,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
request: AddHumanUserRequest;
|
request: AddHumanUserRequest;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -460,12 +427,10 @@ export async function addHuman({
|
|||||||
|
|
||||||
export async function verifyTOTPRegistration({
|
export async function verifyTOTPRegistration({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
code,
|
code,
|
||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
code: string;
|
code: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -479,11 +444,9 @@ export async function verifyTOTPRegistration({
|
|||||||
|
|
||||||
export async function getUserByID({
|
export async function getUserByID({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -494,14 +457,27 @@ export async function getUserByID({
|
|||||||
return userService.getUserByID({ userId }, {});
|
return userService.getUserByID({ userId }, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function humanMFAInitSkipped({
|
||||||
|
serviceUrl,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
serviceUrl: string;
|
||||||
|
userId: string;
|
||||||
|
}) {
|
||||||
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
|
UserService,
|
||||||
|
serviceUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
return userService.humanMFAInitSkipped({ userId }, {});
|
||||||
|
}
|
||||||
|
|
||||||
export async function verifyInviteCode({
|
export async function verifyInviteCode({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
verificationCode,
|
verificationCode,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
verificationCode: string;
|
verificationCode: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -515,11 +491,9 @@ export async function verifyInviteCode({
|
|||||||
|
|
||||||
export async function resendInviteCode({
|
export async function resendInviteCode({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -532,12 +506,10 @@ export async function resendInviteCode({
|
|||||||
|
|
||||||
export async function sendEmailCode({
|
export async function sendEmailCode({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
urlTemplate,
|
urlTemplate,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
urlTemplate: string;
|
urlTemplate: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -563,12 +535,10 @@ export async function sendEmailCode({
|
|||||||
|
|
||||||
export async function createInviteCode({
|
export async function createInviteCode({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
urlTemplate,
|
urlTemplate,
|
||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
urlTemplate: string;
|
urlTemplate: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -600,7 +570,6 @@ export async function createInviteCode({
|
|||||||
|
|
||||||
export type ListUsersCommand = {
|
export type ListUsersCommand = {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
loginName?: string;
|
loginName?: string;
|
||||||
userName?: string;
|
userName?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
@@ -610,7 +579,6 @@ export type ListUsersCommand = {
|
|||||||
|
|
||||||
export async function listUsers({
|
export async function listUsers({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
loginName,
|
loginName,
|
||||||
userName,
|
userName,
|
||||||
phone,
|
phone,
|
||||||
@@ -709,7 +677,6 @@ export async function listUsers({
|
|||||||
|
|
||||||
export type SearchUsersCommand = {
|
export type SearchUsersCommand = {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
searchValue: string;
|
searchValue: string;
|
||||||
loginSettings: LoginSettings;
|
loginSettings: LoginSettings;
|
||||||
organizationId?: string;
|
organizationId?: string;
|
||||||
@@ -755,7 +722,6 @@ const EmailQuery = (searchValue: string) =>
|
|||||||
* */
|
* */
|
||||||
export async function searchUsers({
|
export async function searchUsers({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
searchValue,
|
searchValue,
|
||||||
loginSettings,
|
loginSettings,
|
||||||
organizationId,
|
organizationId,
|
||||||
@@ -900,11 +866,9 @@ export async function getDefaultOrg({
|
|||||||
|
|
||||||
export async function getOrgsByDomain({
|
export async function getOrgsByDomain({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
domain,
|
domain,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
domain: string;
|
domain: string;
|
||||||
}) {
|
}) {
|
||||||
const orgService: Client<typeof OrganizationService> =
|
const orgService: Client<typeof OrganizationService> =
|
||||||
@@ -927,7 +891,6 @@ export async function getOrgsByDomain({
|
|||||||
|
|
||||||
export async function startIdentityProviderFlow({
|
export async function startIdentityProviderFlow({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
idpId,
|
idpId,
|
||||||
urls,
|
urls,
|
||||||
}: {
|
}: {
|
||||||
@@ -952,7 +915,6 @@ export async function startIdentityProviderFlow({
|
|||||||
|
|
||||||
export async function retrieveIdentityProviderInformation({
|
export async function retrieveIdentityProviderInformation({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
idpIntentId,
|
idpIntentId,
|
||||||
idpIntentToken,
|
idpIntentToken,
|
||||||
}: {
|
}: {
|
||||||
@@ -974,11 +936,9 @@ export async function retrieveIdentityProviderInformation({
|
|||||||
|
|
||||||
export async function getAuthRequest({
|
export async function getAuthRequest({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
authRequestId,
|
authRequestId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
authRequestId: string;
|
authRequestId: string;
|
||||||
}) {
|
}) {
|
||||||
const oidcService = await createServiceForHost(OIDCService, serviceUrl);
|
const oidcService = await createServiceForHost(OIDCService, serviceUrl);
|
||||||
@@ -990,11 +950,9 @@ export async function getAuthRequest({
|
|||||||
|
|
||||||
export async function createCallback({
|
export async function createCallback({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
req,
|
req,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
req: CreateCallbackRequest;
|
req: CreateCallbackRequest;
|
||||||
}) {
|
}) {
|
||||||
const oidcService = await createServiceForHost(OIDCService, serviceUrl);
|
const oidcService = await createServiceForHost(OIDCService, serviceUrl);
|
||||||
@@ -1002,14 +960,38 @@ export async function createCallback({
|
|||||||
return oidcService.createCallback(req);
|
return oidcService.createCallback(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getSAMLRequest({
|
||||||
|
serviceUrl,
|
||||||
|
samlRequestId,
|
||||||
|
}: {
|
||||||
|
serviceUrl: string;
|
||||||
|
samlRequestId: string;
|
||||||
|
}) {
|
||||||
|
const samlService = await createServiceForHost(SAMLService, serviceUrl);
|
||||||
|
|
||||||
|
return samlService.getSAMLRequest({
|
||||||
|
samlRequestId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createResponse({
|
||||||
|
serviceUrl,
|
||||||
|
req,
|
||||||
|
}: {
|
||||||
|
serviceUrl: string;
|
||||||
|
req: CreateResponseRequest;
|
||||||
|
}) {
|
||||||
|
const samlService = await createServiceForHost(SAMLService, serviceUrl);
|
||||||
|
|
||||||
|
return samlService.createResponse(req);
|
||||||
|
}
|
||||||
|
|
||||||
export async function verifyEmail({
|
export async function verifyEmail({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
verificationCode,
|
verificationCode,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
verificationCode: string;
|
verificationCode: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -1029,12 +1011,10 @@ export async function verifyEmail({
|
|||||||
|
|
||||||
export async function resendEmailCode({
|
export async function resendEmailCode({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
urlTemplate,
|
urlTemplate,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
urlTemplate: string;
|
urlTemplate: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -1058,12 +1038,10 @@ export async function resendEmailCode({
|
|||||||
|
|
||||||
export async function retrieveIDPIntent({
|
export async function retrieveIDPIntent({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
id,
|
id,
|
||||||
token,
|
token,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
token: string;
|
token: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -1080,11 +1058,9 @@ export async function retrieveIDPIntent({
|
|||||||
|
|
||||||
export async function getIDPByID({
|
export async function getIDPByID({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
id,
|
id,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
}) {
|
}) {
|
||||||
const idpService: Client<typeof IdentityProviderService> =
|
const idpService: Client<typeof IdentityProviderService> =
|
||||||
@@ -1095,12 +1071,10 @@ export async function getIDPByID({
|
|||||||
|
|
||||||
export async function addIDPLink({
|
export async function addIDPLink({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
idp,
|
idp,
|
||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
idp: { id: string; userId: string; userName: string };
|
idp: { id: string; userId: string; userName: string };
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -1124,12 +1098,10 @@ export async function addIDPLink({
|
|||||||
|
|
||||||
export async function passwordReset({
|
export async function passwordReset({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
urlTemplate,
|
urlTemplate,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
urlTemplate?: string;
|
urlTemplate?: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -1161,14 +1133,12 @@ export async function passwordReset({
|
|||||||
|
|
||||||
export async function setUserPassword({
|
export async function setUserPassword({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
password,
|
password,
|
||||||
user,
|
user,
|
||||||
code,
|
code,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
password: string;
|
password: string;
|
||||||
user: User;
|
user: User;
|
||||||
@@ -1224,11 +1194,9 @@ export async function setUserPassword({
|
|||||||
|
|
||||||
export async function setPassword({
|
export async function setPassword({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
payload,
|
payload,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
payload: SetPasswordRequest;
|
payload: SetPasswordRequest;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -1247,11 +1215,9 @@ export async function setPassword({
|
|||||||
*/
|
*/
|
||||||
export async function createPasskeyRegistrationLink({
|
export async function createPasskeyRegistrationLink({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -1277,12 +1243,10 @@ export async function createPasskeyRegistrationLink({
|
|||||||
*/
|
*/
|
||||||
export async function registerU2F({
|
export async function registerU2F({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
domain,
|
domain,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -1305,11 +1269,9 @@ export async function registerU2F({
|
|||||||
*/
|
*/
|
||||||
export async function verifyU2FRegistration({
|
export async function verifyU2FRegistration({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
request,
|
request,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
request: VerifyU2FRegistrationRequest;
|
request: VerifyU2FRegistrationRequest;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -1329,12 +1291,10 @@ export async function verifyU2FRegistration({
|
|||||||
*/
|
*/
|
||||||
export async function getActiveIdentityProviders({
|
export async function getActiveIdentityProviders({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
orgId,
|
orgId,
|
||||||
linking_allowed,
|
linking_allowed,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
linking_allowed?: boolean;
|
linking_allowed?: boolean;
|
||||||
}) {
|
}) {
|
||||||
@@ -1356,11 +1316,9 @@ export async function getActiveIdentityProviders({
|
|||||||
*/
|
*/
|
||||||
export async function verifyPasskeyRegistration({
|
export async function verifyPasskeyRegistration({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
request,
|
request,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
request: VerifyPasskeyRegistrationRequest;
|
request: VerifyPasskeyRegistrationRequest;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
@@ -1381,13 +1339,11 @@ export async function verifyPasskeyRegistration({
|
|||||||
*/
|
*/
|
||||||
export async function registerPasskey({
|
export async function registerPasskey({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
|
||||||
userId,
|
userId,
|
||||||
code,
|
code,
|
||||||
domain,
|
domain,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
code: { id: string; code: string };
|
code: { id: string; code: string };
|
||||||
domain: string;
|
domain: string;
|
||||||
@@ -1415,7 +1371,6 @@ export async function listAuthenticationMethodTypes({
|
|||||||
userId,
|
userId,
|
||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
|
@@ -4,6 +4,7 @@ import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_servi
|
|||||||
import { RequestContextSchema } from "@zitadel/proto/zitadel/object/v2/object_pb.js";
|
import { RequestContextSchema } from "@zitadel/proto/zitadel/object/v2/object_pb.js";
|
||||||
import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb.js";
|
import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb.js";
|
||||||
import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb.js";
|
import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb.js";
|
||||||
|
import { SAMLService } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb.js";
|
||||||
import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_service_pb.js";
|
import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_service_pb.js";
|
||||||
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb.js";
|
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb.js";
|
||||||
import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_pb.js";
|
import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_pb.js";
|
||||||
@@ -14,6 +15,7 @@ export const createUserServiceClient = createClientFor(UserService);
|
|||||||
export const createSettingsServiceClient = createClientFor(SettingsService);
|
export const createSettingsServiceClient = createClientFor(SettingsService);
|
||||||
export const createSessionServiceClient = createClientFor(SessionService);
|
export const createSessionServiceClient = createClientFor(SessionService);
|
||||||
export const createOIDCServiceClient = createClientFor(OIDCService);
|
export const createOIDCServiceClient = createClientFor(OIDCService);
|
||||||
|
export const createSAMLServiceClient = createClientFor(SAMLService);
|
||||||
export const createOrganizationServiceClient = createClientFor(OrganizationService);
|
export const createOrganizationServiceClient = createClientFor(OrganizationService);
|
||||||
export const createFeatureServiceClient = createClientFor(FeatureService);
|
export const createFeatureServiceClient = createClientFor(FeatureService);
|
||||||
export const createIdpServiceClient = createClientFor(IdentityProviderService);
|
export const createIdpServiceClient = createClientFor(IdentityProviderService);
|
||||||
|
Reference in New Issue
Block a user