-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #65 from zitadel/qa
Multifactor Authentication
- Loading branch information
Showing
39 changed files
with
4,932 additions
and
6,172 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
Oops, something went wrong.