Skip to content

Commit

Permalink
Merge pull request #65 from zitadel/qa
Browse files Browse the repository at this point in the history
Multifactor Authentication
  • Loading branch information
peintnermax authored May 8, 2024
2 parents 862df4b + 2b160b1 commit cd1cbe4
Show file tree
Hide file tree
Showing 39 changed files with 4,932 additions and 6,172 deletions.
13 changes: 9 additions & 4 deletions apps/login/app/(login)/idp/[provider]/success/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export default async function Page({
return retrieveIDPIntent(id, token)
.then((resp) => {
const { idpInformation, userId } = resp;

if (idpInformation) {
// handle login
if (userId) {
Expand Down Expand Up @@ -166,10 +167,14 @@ export default async function Page({
});
} else {
return (
<div className="flex flex-col items-center space-y-4">
<h1>Register</h1>
<p className="ztdl-p">No id and token received!</p>
</div>
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<div className="flex flex-col items-center space-y-4">
<h1>Register</h1>
<p className="ztdl-p">No id and token received!</p>
</div>
</div>
</DynamicTheme>
);
}
}
35 changes: 0 additions & 35 deletions apps/login/app/(login)/mfa/create/page.tsx

This file was deleted.

103 changes: 101 additions & 2 deletions apps/login/app/(login)/mfa/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,102 @@
export default function Page() {
return <div className="flex flex-col items-center space-y-4">mfa</div>;
import {
getBrandingSettings,
getSession,
listAuthenticationMethodTypes,
server,
} from "#/lib/zitadel";
import Alert from "#/ui/Alert";
import ChooseSecondFactor from "#/ui/ChooseSecondFactor";
import DynamicTheme from "#/ui/DynamicTheme";
import UserAvatar from "#/ui/UserAvatar";
import {
getMostRecentCookieWithLoginname,
getSessionCookieById,
} from "#/utils/cookies";

export default async function Page({
searchParams,
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const { loginName, checkAfter, authRequestId, organization, sessionId } =
searchParams;

const sessionFactors = sessionId
? await loadSessionById(sessionId, organization)
: await loadSessionByLoginname(loginName, organization);

async function loadSessionByLoginname(
loginName?: string,
organization?: string
) {
const recent = await getMostRecentCookieWithLoginname(
loginName,
organization
);
return getSession(server, recent.id, recent.token).then((response) => {
if (response?.session && response.session.factors?.user?.id) {
return listAuthenticationMethodTypes(
response.session.factors.user.id
).then((methods) => {
return {
factors: response.session?.factors,
authMethods: methods.authMethodTypes ?? [],
};
});
}
});
}

async function loadSessionById(sessionId: string, organization?: string) {
const recent = await getSessionCookieById(sessionId, organization);
return getSession(server, recent.id, recent.token).then((response) => {
if (response?.session && response.session.factors?.user?.id) {
return listAuthenticationMethodTypes(
response.session.factors.user.id
).then((methods) => {
return {
factors: response.session?.factors,
authMethods: methods.authMethodTypes ?? [],
};
});
}
});
}

const branding = await getBrandingSettings(server, organization);

return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Verify 2-Factor</h1>

<p className="ztdl-p">Choose one of the following second factors.</p>

{sessionFactors && (
<UserAvatar
loginName={loginName ?? sessionFactors.factors?.user?.loginName}
displayName={sessionFactors.factors?.user?.displayName}
showDropdown
searchParams={searchParams}
></UserAvatar>
)}

{!(loginName || sessionId) && (
<Alert>Provide your active session as loginName param</Alert>
)}

{sessionFactors ? (
<ChooseSecondFactor
loginName={loginName}
sessionId={sessionId}
authRequestId={authRequestId}
organization={organization}
userMethods={sessionFactors.authMethods ?? []}
></ChooseSecondFactor>
) : (
<Alert>No second factors available to setup.</Alert>
)}
</div>
</DynamicTheme>
);
}
136 changes: 109 additions & 27 deletions apps/login/app/(login)/mfa/set/page.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,116 @@
"use client";
import { Button, ButtonVariants } from "#/ui/Button";
import { TextInput } from "#/ui/Input";
import {
getBrandingSettings,
getLoginSettings,
getSession,
getUserByID,
listAuthenticationMethodTypes,
server,
} from "#/lib/zitadel";
import Alert from "#/ui/Alert";
import ChooseSecondFactorToSetup from "#/ui/ChooseSecondFactorToSetup";
import DynamicTheme from "#/ui/DynamicTheme";
import UserAvatar from "#/ui/UserAvatar";
import { useRouter } from "next/navigation";
import {
getMostRecentCookieWithLoginname,
getSessionCookieById,
} from "#/utils/cookies";
import { user } from "@zitadel/server";

export default function Page() {
const router = useRouter();
export default async function Page({
searchParams,
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const { loginName, checkAfter, authRequestId, organization, sessionId } =
searchParams;

const sessionWithData = sessionId
? await loadSessionById(sessionId, organization)
: await loadSessionByLoginname(loginName, organization);

async function loadSessionByLoginname(
loginName?: string,
organization?: string
) {
const recent = await getMostRecentCookieWithLoginname(
loginName,
organization
);
return getSession(server, recent.id, recent.token).then((response) => {
if (response?.session && response.session.factors?.user?.id) {
const userId = response.session.factors.user.id;
return listAuthenticationMethodTypes(userId).then((methods) => {
return getUserByID(userId).then((user) => {
return {
factors: response.session?.factors,
authMethods: methods.authMethodTypes ?? [],
phoneVerified: user.user?.human?.phone?.isVerified ?? false,
emailVerified: user.user?.human?.email?.isVerified ?? false,
};
});
});
}
});
}

async function loadSessionById(sessionId: string, organization?: string) {
const recent = await getSessionCookieById(sessionId, organization);
return getSession(server, recent.id, recent.token).then((response) => {
if (response?.session && response.session.factors?.user?.id) {
const userId = response.session.factors.user.id;
return listAuthenticationMethodTypes(userId).then((methods) => {
return getUserByID(userId).then((user) => {
return {
factors: response.session?.factors,
authMethods: methods.authMethodTypes ?? [],
phoneVerified: user.user?.human?.phone?.isVerified ?? false,
emailVerified: user.user?.human?.email?.isVerified ?? false,
};
});
});
}
});
}

const branding = await getBrandingSettings(server, organization);
const loginSettings = await getLoginSettings(server, organization);

return (
<div className="flex flex-col items-center space-y-4">
<h1>Password</h1>
<p className="ztdl-p mb-6 block">Enter your password.</p>

<UserAvatar
showDropdown
displayName="Max Peintner"
loginName="[email protected]"
></UserAvatar>
<div className="w-full">
<TextInput type="password" label="Password" />
</div>
<div className="flex w-full flex-row items-center justify-between">
<Button
onClick={() => router.back()}
variant={ButtonVariants.Secondary}
>
back
</Button>
<Button variant={ButtonVariants.Primary}>continue</Button>
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Set up 2-Factor</h1>

<p className="ztdl-p">Choose one of the following second factors.</p>

{sessionWithData && (
<UserAvatar
loginName={loginName ?? sessionWithData.factors?.user?.loginName}
displayName={sessionWithData.factors?.user?.displayName}
showDropdown
searchParams={searchParams}
></UserAvatar>
)}

{!(loginName || sessionId) && (
<Alert>Provide your active session as loginName param</Alert>
)}

{loginSettings && sessionWithData ? (
<ChooseSecondFactorToSetup
loginName={loginName}
sessionId={sessionId}
authRequestId={authRequestId}
organization={organization}
loginSettings={loginSettings}
userMethods={sessionWithData.authMethods ?? []}
phoneVerified={sessionWithData.phoneVerified ?? false}
emailVerified={sessionWithData.emailVerified ?? false}
checkAfter={checkAfter === "true"}
></ChooseSecondFactorToSetup>
) : (
<Alert>No second factors available to setup.</Alert>
)}
</div>
</div>
</DynamicTheme>
);
}
84 changes: 84 additions & 0 deletions apps/login/app/(login)/otp/[method]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
getBrandingSettings,
getLoginSettings,
getSession,
server,
} from "#/lib/zitadel";
import Alert from "#/ui/Alert";
import DynamicTheme from "#/ui/DynamicTheme";
import LoginOTP from "#/ui/LoginOTP";
import UserAvatar from "#/ui/UserAvatar";
import { getMostRecentCookieWithLoginname } from "#/utils/cookies";

export default async function Page({
searchParams,
params,
}: {
searchParams: Record<string | number | symbol, string | undefined>;
params: Record<string | number | symbol, string | undefined>;
}) {
const { loginName, authRequestId, sessionId, organization, code, submit } =
searchParams;

const { method } = params;

const { session, token } = await loadSession(loginName, organization);

const branding = await getBrandingSettings(server, organization);

async function loadSession(loginName?: string, organization?: string) {
const recent = await getMostRecentCookieWithLoginname(
loginName,
organization
);

return getSession(server, recent.id, recent.token).then((response) => {
return { session: response?.session, token: recent.token };
});
}

return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Verify 2-Factor</h1>
{method === "time-based" && (
<p className="ztdl-p">Enter the code from your authenticator app.</p>
)}
{method === "sms" && (
<p className="ztdl-p">Enter the code you got on your phone.</p>
)}
{method === "email" && (
<p className="ztdl-p">Enter the code you got via your email.</p>
)}

{!session && (
<div className="py-4">
<Alert>
Could not get the context of the user. Make sure to enter the
username first or provide a loginName as searchParam.
</Alert>
</div>
)}

{session && (
<UserAvatar
loginName={loginName ?? session.factors?.user?.loginName}
displayName={session.factors?.user?.displayName}
showDropdown
searchParams={searchParams}
></UserAvatar>
)}

{method && (
<LoginOTP
loginName={loginName}
sessionId={sessionId}
authRequestId={authRequestId}
organization={organization}
method={method}
></LoginOTP>
)}
</div>
</DynamicTheme>
);
}
Loading

0 comments on commit cd1cbe4

Please sign in to comment.