passkey verify, page styles

This commit is contained in:
Max Peintner
2023-06-15 13:58:32 +02:00
parent 69301d789d
commit 73d5c6e70c
12 changed files with 1791 additions and 1586 deletions

View File

@@ -24,14 +24,31 @@ export default async function Page({
console.log(sessionFactors);
return (
<div className="flex flex-col items-center space-y-4">
<h1>Register Passkey</h1>
<h1>Use your passkey to confirm its really you</h1>
{sessionFactors && (
<UserAvatar
loginName={loginName ?? sessionFactors.factors?.user?.loginName ?? ""}
displayName={sessionFactors.factors?.user?.displayName}
showDropdown
></UserAvatar>
)}
<p className="ztdl-p mb-6 block">
Setup your user to authenticate with passkeys.
Your device will ask for your fingerprint, face, or screen lock
</p>
<Alert type={AlertType.INFO}>
A passkey is an authentication method on a device like your fingerprint,
Apple FaceID or similar.
<span>
A passkey is an authentication method on a device like your
fingerprint, Apple FaceID or similar.{" "}
<a
className="text-primary-light-500 dark:text-primary-dark-500 hover:text-primary-light-300 hover:dark:text-primary-dark-300"
target="_blank"
href="https://zitadel.com/docs/guides/manage/user/reg-create-user#with-passwordless"
>
Passwordless Authentication
</a>
</span>
</Alert>
{!sessionFactors && (
@@ -43,14 +60,6 @@ export default async function Page({
</div>
)}
{sessionFactors && (
<UserAvatar
loginName={loginName ?? sessionFactors.factors?.user?.loginName ?? ""}
displayName={sessionFactors.factors?.user?.displayName}
showDropdown
></UserAvatar>
)}
{sessionFactors?.id && <RegisterPasskey sessionId={sessionFactors.id} />}
</div>
);

View File

@@ -23,14 +23,16 @@ export async function POST(request: NextRequest) {
const userId = session?.session?.factors?.user?.id;
if (userId) {
return createPasskeyRegistrationLink(userId, sessionCookie.token)
return createPasskeyRegistrationLink(userId)
.then((resp) => {
const code = resp.code;
console.log("code", code);
return registerPasskey(userId, code).then((resp) => {
return NextResponse.json(resp);
});
})
.catch((error) => {
console.log("error on creating passkey registration link");
return NextResponse.json(error, { status: 500 });
});
} else {
@@ -40,6 +42,6 @@ export async function POST(request: NextRequest) {
);
}
} else {
return NextResponse.json({}, { status: 500 });
return NextResponse.json({}, { status: 400 });
}
}

View File

@@ -0,0 +1,51 @@
import {
createPasskeyRegistrationLink,
getSession,
registerPasskey,
server,
verifyPasskeyRegistration,
} from "#/lib/zitadel";
import { getSessionCookieById } from "#/utils/cookies";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const body = await request.json();
if (body) {
const { passkeyId, passkeyName, publicKeyCredential, sessionId } = body;
const sessionCookie = await getSessionCookieById(sessionId);
const session = await getSession(
server,
sessionCookie.id,
sessionCookie.token
);
const userId = session?.session?.factors?.user?.id;
if (userId) {
return verifyPasskeyRegistration(
server,
passkeyId,
passkeyName,
publicKeyCredential,
userId
)
.then((resp) => {
console.log("verifyresponse", resp);
return NextResponse.json(resp);
})
.catch((error) => {
console.log("error on verifying passkey");
return NextResponse.json(error, { status: 500 });
});
} else {
return NextResponse.json(
{ details: "could not get session" },
{ status: 500 }
);
}
} else {
return NextResponse.json({}, { status: 400 });
}
}

View File

@@ -20,7 +20,9 @@ import {
VerifyEmailResponse,
SetSessionResponse,
DeleteSessionResponse,
VerifyPasskeyRegistrationRequest,
VerifyPasskeyRegistrationResponse,
orgMetadata,
} from "@zitadel/server";
import { Metadata } from "nice-grpc";
@@ -196,26 +198,14 @@ const bearerTokenMetadata = (token: string) =>
* @returns the newly set email
*/
export async function createPasskeyRegistrationLink(
userId: string,
sessionToken: string
userId: string
): Promise<any> {
// this actions will be made from the currently seleected user
// const zitadelConfig: ZitadelServerOptions = {
// name: "zitadel login",
// apiUrl: process.env.ZITADEL_API_URL ?? "",
// token: "",
// };
// const authserver: ZitadelServer = initializeServer(zitadelConfig);
// console.log("server", authserver);
const userservice = user.getUser(server);
return userservice.createPasskeyRegistrationLink(
{
userId,
returnCode: {},
}
// { metadata: bearerTokenMetadata(sessionToken) }
);
return userservice.createPasskeyRegistrationLink({
userId,
returnCode: {},
});
}
/**
@@ -228,10 +218,15 @@ export async function verifyPasskeyRegistration(
server: ZitadelServer,
passkeyId: string,
passkeyName: string,
publicKeyCredential: any,
publicKeyCredential:
| {
[key: string]: any;
}
| undefined,
userId: string
): Promise<VerifyPasskeyRegistrationResponse> {
const userservice = user.getUser(server);
console.log(passkeyId, passkeyName, publicKeyCredential, userId);
return userservice.verifyPasskeyRegistration(
{
passkeyId,

View File

@@ -9,7 +9,7 @@
@layer base {
h1,
.ztdl-h1 {
@apply text-2xl;
@apply text-2xl text-center;
}
.ztdl-p {

View File

@@ -1,4 +1,7 @@
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import {
ExclamationTriangleIcon,
InformationCircleIcon,
} from "@heroicons/react/24/outline";
import clsx from "clsx";
type Props = {
@@ -29,8 +32,13 @@ export default function Alert({ children, type = AlertType.ALERT }: Props) {
}
)}
>
<ExclamationTriangleIcon className="flex-shrink-0 h-5 w-5 mr-2 ml-2" />
<span className="text-center text-sm">{children}</span>
{type === AlertType.ALERT && (
<ExclamationTriangleIcon className="flex-shrink-0 h-5 w-5 mr-2 ml-2" />
)}
{type === AlertType.INFO && (
<InformationCircleIcon className="flex-shrink-0 h-5 w-5 mr-2 ml-2" />
)}
<span className="text-sm">{children}</span>
</div>
);
}

View File

@@ -50,9 +50,40 @@ export default function RegisterPasskey({ sessionId }: Props) {
return response;
}
async function submitVerify(
passkeyId: string,
passkeyName: string,
publicKeyCredential: any,
sessionId: string
) {
setLoading(true);
const res = await fetch("/passkeys/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
passkeyId,
passkeyName,
publicKeyCredential,
sessionId,
}),
});
const response = await res.json();
setLoading(false);
if (!res.ok) {
setError(response.details);
return Promise.reject(response.details);
}
return response;
}
function submitRegisterAndContinue(value: Inputs): Promise<boolean | void> {
return submitRegister().then((resp: RegisterPasskeyResponse) => {
console.log(resp.publicKeyCredentialCreationOptions?.publicKey);
const passkeyId = resp.passkeyId;
console.log("passkeyId", passkeyId);
if (
resp.publicKeyCredentialCreationOptions &&
resp.publicKeyCredentialCreationOptions.publicKey
@@ -96,10 +127,11 @@ export default function RegisterPasskey({ sessionId }: Props) {
const clientDataJSON = (resp as any).response.clientDataJSON;
const rawId = (resp as any).rawId;
const data = JSON.stringify({
const data = {
id: resp.id,
rawId: coerceToBase64Url(rawId, "rawId"),
type: resp.type,
// response: (resp as any).response,
response: {
attestationObject: coerceToBase64Url(
attestationObject,
@@ -110,11 +142,10 @@ export default function RegisterPasskey({ sessionId }: Props) {
"clientDataJSON"
),
},
});
};
console.log(data);
const base64 = btoa(data);
return base64;
return submitVerify(passkeyId, "name", data, sessionId);
// if (this.type === U2FComponentDestination.MFA) {
// this.service
// .verifyMyMultiFactorU2F(base64, this.name)

View File

@@ -14,7 +14,7 @@ export default function UserAvatar({
showDropdown,
}: Props) {
return (
<div className="flex h-full w-full flex-row items-center rounded-full border p-[1px] dark:border-white/20">
<div className="flex h-full flex-row items-center rounded-full border p-[1px] dark:border-white/20">
<div>
<Avatar
size="small"
@@ -27,7 +27,7 @@ export default function UserAvatar({
{showDropdown && (
<Link
href="/accounts"
className="flex items-center justify-center p-1 hover:bg-black/10 dark:hover:bg-white/10 rounded-full mr-1 transition-all"
className="ml-4 flex items-center justify-center p-1 hover:bg-black/10 dark:hover:bg-white/10 rounded-full mr-1 transition-all"
>
<ChevronDownIcon className="h-4 w-4" />
</Link>