Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multifactor Authentication #65

Merged
merged 49 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
d7f7fef
totp page, totp api
peintnermax Apr 4, 2024
57e4585
redirect to totp from password form
peintnermax Apr 4, 2024
437ba43
mfa selection, setup page
peintnermax Apr 12, 2024
cee9c27
set mfa page, auth service
peintnermax Apr 15, 2024
1a06f42
cleanup setsession
peintnermax Apr 16, 2024
2654c57
url params
peintnermax Apr 16, 2024
a8b2a9c
loginsettings for password page
peintnermax Apr 16, 2024
80e2f3e
fix failed import
peintnermax Apr 16, 2024
57db64f
otp methods
peintnermax Apr 16, 2024
20a589c
combined otp form
peintnermax Apr 17, 2024
346f13e
set checks to session endpoint
peintnermax Apr 17, 2024
c4a6af4
get email and phone from user resource
peintnermax Apr 17, 2024
8e23e2d
provide domain for u2f
peintnermax Apr 17, 2024
1a4f33c
rm alternative pwd btn
peintnermax Apr 17, 2024
a065aac
fix authRequestId param
peintnermax Apr 17, 2024
a9901d4
skip completes oidc flow
peintnermax Apr 17, 2024
42df2c4
u2f pages, choose 2 factor page
peintnermax Apr 18, 2024
b78e506
switch links
peintnermax Apr 18, 2024
4f9e7d7
register totp, login catch expired session
peintnermax Apr 22, 2024
44435ad
account selection on expired token
peintnermax Apr 22, 2024
827af38
show already setupped methods
peintnermax Apr 23, 2024
430d87c
state on mfa set
peintnermax Apr 23, 2024
6622288
rm u2f set cmp
peintnermax Apr 24, 2024
71a6b14
fix authrequestid param
peintnermax Apr 24, 2024
e8738ce
rm import
peintnermax Apr 24, 2024
a4f5991
catch error on callback
peintnermax Apr 25, 2024
0d954f0
return redirect
peintnermax Apr 25, 2024
44bf7ac
fix error message on /password
peintnermax Apr 25, 2024
a6a5f72
mfa set cleanup
peintnermax Apr 25, 2024
1e7bbc4
put instead of post
peintnermax Apr 26, 2024
b751838
fix: session callback on otp register
peintnermax Apr 29, 2024
83f9445
resp on setup
peintnermax Apr 29, 2024
ec20bb4
set context in url for method
peintnermax Apr 29, 2024
9a9ae48
wrong searchparam
peintnermax Apr 29, 2024
88030ff
checkafter redirect, continue after setup
peintnermax Apr 29, 2024
b01ca12
choose factor when multiple, register u2f, verify u2f
peintnermax Apr 30, 2024
65bdb31
use v2beta alternative method
peintnermax Apr 30, 2024
f82fba7
method links
peintnermax Apr 30, 2024
072dea4
authRequestId param
peintnermax Apr 30, 2024
fd8a1a3
cleanup password form next step
peintnermax Apr 30, 2024
39d54e2
account selection with params
peintnermax May 3, 2024
a74f728
remove createpasskeycreation code for u2f
peintnermax May 3, 2024
6c27c44
u2f registration endpoint
peintnermax May 7, 2024
a85eb93
rm session token from totp register request
peintnermax May 7, 2024
6764e6f
u2fId
peintnermax May 7, 2024
92e9150
add resend capblty for sms, email otp
peintnermax May 7, 2024
09194d1
hide mfa method from selection if phone or email is not verified
peintnermax May 7, 2024
47b1dfe
add comment about checking sessions
peintnermax May 7, 2024
2b160b1
remove dead code
peintnermax May 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading