mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 06:52:24 +00:00
passkey verify, page styles
This commit is contained in:
@@ -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 it’s 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>
|
||||
);
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
51
apps/login/app/passkeys/verify/route.ts
Normal file
51
apps/login/app/passkeys/verify/route.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
@layer base {
|
||||
h1,
|
||||
.ztdl-h1 {
|
||||
@apply text-2xl;
|
||||
@apply text-2xl text-center;
|
||||
}
|
||||
|
||||
.ztdl-p {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user