fix client error

This commit is contained in:
Max Peintner
2023-06-30 14:13:03 +02:00
parent f324407a83
commit 9ba466387b
21 changed files with 140 additions and 120 deletions

View File

@@ -1,30 +1,30 @@
import { getSession, server } from "#/lib/zitadel";
import Alert from "#/ui/Alert"; import Alert from "#/ui/Alert";
import LoginPasskey from "#/ui/LoginPasskey"; import LoginPasskey from "#/ui/LoginPasskey";
import { ChallengeKind } from "@zitadel/server"; import UserAvatar from "#/ui/UserAvatar";
import { getMostRecentCookieWithLoginname } from "#/utils/cookies";
// import LoginPasskey from "#/ui/LoginPasskey";
// import {
// SessionCookie,
// getMostRecentSessionCookie,
// getSessionCookieByLoginName,
// } from "#/utils/cookies";
// import { setSessionAndUpdateCookie } from "#/utils/session";
// import { ChallengeKind } from "@zitadel/server";
async function updateSessionAndCookie(loginName: string) { // async function updateSessionAndCookie(loginName: string) {
const res = await fetch( // return getSessionCookieByLoginName(loginName).then((recent) => {
`${process.env.VERCEL_URL ?? "http://localhost:3000"}/session`, // console.log(recent.token);
{ // return setSessionAndUpdateCookie(
method: "PUT", // recent.id,
headers: { // recent.token,
"Content-Type": "application/json", // recent.loginName,
}, // undefined,
body: JSON.stringify({ // "localhost",
loginName, // [ChallengeKind.CHALLENGE_KIND_PASSKEY]
challenges: [ChallengeKind.CHALLENGE_KIND_PASSKEY], // );
}), // });
next: { revalidate: 0 }, // }
}
);
const response = await res.json();
if (!res.ok) {
return Promise.reject(response.details);
}
return response;
}
const title = "Authenticate with a passkey"; const title = "Authenticate with a passkey";
const description = const description =
@@ -36,20 +36,23 @@ export default async function Page({
searchParams: Record<string | number | symbol, string | undefined>; searchParams: Record<string | number | symbol, string | undefined>;
}) { }) {
const { loginName } = searchParams; const { loginName } = searchParams;
if (loginName) {
console.log(loginName);
const session = await updateSessionAndCookie(loginName);
console.log("sess", session); const sessionFactors = await loadSession(loginName);
const challenge = session?.challenges?.passkey;
console.log(challenge); async function loadSession(loginName?: string) {
const recent = await getMostRecentCookieWithLoginname(loginName);
return getSession(server, recent.id, recent.token).then((response) => {
if (response?.session) {
return response.session;
}
});
}
return ( return (
<div className="flex flex-col items-center space-y-4"> <div className="flex flex-col items-center space-y-4">
<h1>{title}</h1> <h1>{title}</h1>
{/* {sessionFactors && ( {sessionFactors && (
<UserAvatar <UserAvatar
loginName={loginName ?? sessionFactors.factors?.user?.loginName} loginName={loginName ?? sessionFactors.factors?.user?.loginName}
displayName={sessionFactors.factors?.user?.displayName} displayName={sessionFactors.factors?.user?.displayName}
@@ -58,40 +61,15 @@ export default async function Page({
)} )}
<p className="ztdl-p mb-6 block">{description}</p> <p className="ztdl-p mb-6 block">{description}</p>
{!sessionFactors && ( {!sessionFactors && <div className="py-4"></div>}
<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>
)} */}
{challenge && <LoginPasskey challenge={challenge} />}
</div>
);
} else {
return (
<div className="flex flex-col items-center space-y-4">
<h1>{title}</h1>
{/* {sessionFactors && (
<UserAvatar
loginName={loginName ?? sessionFactors.factors?.user?.loginName}
displayName={sessionFactors.factors?.user?.displayName}
showDropdown
></UserAvatar>
)}
<p className="ztdl-p mb-6 block">{description}</p>
{!sessionFactors && (
<div className="py-4">
</div>
)} */}
{!loginName && (
<Alert>Provide your active session as loginName param</Alert> <Alert>Provide your active session as loginName param</Alert>
</div> )}
);
} {loginName && (
<LoginPasskey challenge={{} as any} loginName={loginName} />
)}
</div>
);
} }

View File

@@ -1,10 +1,11 @@
import { server, deleteSession } from "#/lib/zitadel"; import { server, deleteSession, getSession, setSession } from "#/lib/zitadel";
import { import {
SessionCookie, SessionCookie,
getMostRecentSessionCookie, getMostRecentSessionCookie,
getSessionCookieById, getSessionCookieById,
getSessionCookieByLoginName, getSessionCookieByLoginName,
removeSessionFromCookie, removeSessionFromCookie,
updateSessionCookie,
} from "#/utils/cookies"; } from "#/utils/cookies";
import { import {
createSessionAndUpdateCookie, createSessionAndUpdateCookie,
@@ -59,9 +60,11 @@ export async function PUT(request: NextRequest) {
domain, domain,
challenges challenges
).then((session) => { ).then((session) => {
console.log(session.challenges);
return NextResponse.json({ return NextResponse.json({
sessionId: session.id, sessionId: session.id,
factors: session.factors, factors: session.factors,
challenges: session.challenges,
}); });
}); });
}) })

View File

@@ -37,6 +37,7 @@
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "7.39.5", "react-hook-form": "7.39.5",
"sass": "^1.62.0", "sass": "^1.62.0",
"swr": "^2.2.0",
"tinycolor2": "1.4.2" "tinycolor2": "1.4.2"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,24 +1,48 @@
"use client"; "use client";
import { useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Challenges_Passkey } from "@zitadel/server"; import { ChallengeKind, Challenges_Passkey } from "@zitadel/server";
import { coerceToArrayBuffer, coerceToBase64Url } from "#/utils/base64"; import { coerceToArrayBuffer, coerceToBase64Url } from "#/utils/base64";
import { Button, ButtonVariants } from "./Button"; import { Button, ButtonVariants } from "./Button";
import Alert from "./Alert"; import Alert from "./Alert";
import { Spinner } from "./Spinner"; import { Spinner } from "./Spinner";
type Props = { type Props = {
loginName: string;
challenge: Challenges_Passkey; challenge: Challenges_Passkey;
}; };
export default function LoginPasskey({ challenge }: Props) { export default function LoginPasskey({ loginName, challenge }: Props) {
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const router = useRouter(); const router = useRouter();
useEffect(() => {
updateSessionForChallenge();
}, []);
async function updateSessionForChallenge() {
setLoading(true);
const res = await fetch("/api/session", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
loginName,
challenges: [1], // request passkey challenge
}),
});
setLoading(false);
if (!res.ok) {
throw new Error("Failed to load authentication methods");
}
return res.json();
}
async function submitLogin( async function submitLogin(
passkeyId: string, passkeyId: string,
passkeyName: string, passkeyName: string,
@@ -26,7 +50,7 @@ export default function LoginPasskey({ challenge }: Props) {
sessionId: string sessionId: string
) { ) {
setLoading(true); setLoading(true);
const res = await fetch("/passkeys/verify", { const res = await fetch("/api/passkeys/verify", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -99,13 +123,12 @@ export default function LoginPasskey({ challenge }: Props) {
} }
return ( return (
<form className="w-full"> <div className="w-full">
{error && ( {error && (
<div className="py-4"> <div className="py-4">
<Alert>{error}</Alert> <Alert>{error}</Alert>
</div> </div>
)} )}
<div className="mt-8 flex w-full flex-row items-center"> <div className="mt-8 flex w-full flex-row items-center">
<Button <Button
type="button" type="button"
@@ -127,6 +150,6 @@ export default function LoginPasskey({ challenge }: Props) {
continue continue
</Button> </Button>
</div> </div>
</form> </div>
); );
} }

View File

@@ -30,7 +30,7 @@ export default function PasswordForm({ loginName }: Props) {
async function submitPassword(values: Inputs) { async function submitPassword(values: Inputs) {
setError(""); setError("");
setLoading(true); setLoading(true);
const res = await fetch("/session", { const res = await fetch("/api/session", {
method: "PUT", method: "PUT",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@@ -48,7 +48,7 @@ export default function RegisterForm({
async function submitRegister(values: Inputs) { async function submitRegister(values: Inputs) {
setLoading(true); setLoading(true);
const res = await fetch("/registeruser", { const res = await fetch("/api/registeruser", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@@ -36,7 +36,7 @@ export default function RegisterFormWithoutPassword({ legal }: Props) {
async function submitAndRegister(values: Inputs) { async function submitAndRegister(values: Inputs) {
setLoading(true); setLoading(true);
const res = await fetch("/registeruser", { const res = await fetch("/api/registeruser", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -56,7 +56,7 @@ export default function RegisterFormWithoutPassword({ legal }: Props) {
async function createSessionWithLoginName(loginName: string) { async function createSessionWithLoginName(loginName: string) {
setLoading(true); setLoading(true);
const res = await fetch("/session", { const res = await fetch("/api/session", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@@ -29,7 +29,7 @@ export default function RegisterPasskey({ sessionId, isPrompt }: Props) {
async function submitRegister() { async function submitRegister() {
setError(""); setError("");
setLoading(true); setLoading(true);
const res = await fetch("/passkeys", { const res = await fetch("/api/passkeys", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -56,7 +56,7 @@ export default function RegisterPasskey({ sessionId, isPrompt }: Props) {
sessionId: string sessionId: string
) { ) {
setLoading(true); setLoading(true);
const res = await fetch("/passkeys/verify", { const res = await fetch("/api/passkeys/verify", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@@ -17,7 +17,7 @@ export default function SessionItem({
async function clearSession(id: string) { async function clearSession(id: string) {
setLoading(true); setLoading(true);
const res = await fetch("/session?" + new URLSearchParams({ id }), { const res = await fetch("/api/session?" + new URLSearchParams({ id }), {
method: "DELETE", method: "DELETE",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@@ -47,7 +47,7 @@ export default function SetPasswordForm({
async function submitRegister(values: Inputs) { async function submitRegister(values: Inputs) {
setLoading(true); setLoading(true);
const res = await fetch("/registeruser", { const res = await fetch("/api/registeruser", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -71,7 +71,7 @@ export default function SetPasswordForm({
loginName: string, loginName: string,
password: string password: string
) { ) {
const res = await fetch("/session", { const res = await fetch("/api/session", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@@ -31,7 +31,7 @@ export default function UsernameForm({ loginSettings, loginName }: Props) {
async function submitLoginName(values: Inputs) { async function submitLoginName(values: Inputs) {
setLoading(true); setLoading(true);
const res = await fetch("/loginnames", { const res = await fetch("/api/loginname", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -53,25 +53,24 @@ export default function UsernameForm({ loginSettings, loginName }: Props) {
console.log(response); console.log(response);
if (response.authMethodTypes.length == 1) { if (response.authMethodTypes.length == 1) {
const method = response.authMethodTypes[0]; const method = response.authMethodTypes[0];
console.log(method); switch (method) {
// switch (method) { case 1: //AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSWORD:
// case AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSWORD: return router.push(
// return router.push( "/password?" +
// "/password?" + new URLSearchParams({ loginName: values.loginName })
// new URLSearchParams({ loginName: values.loginName }) );
// ); case 2: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
// case AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY: return router.push(
// break; "/passkey/login?" +
// // return router.push( new URLSearchParams({ loginName: values.loginName })
// // "/passkey/login?" + );
// // new URLSearchParams({ loginName: values.loginName }) default:
// // ); return router.push(
// default: "/password?" +
// return router.push( new URLSearchParams({ loginName: values.loginName })
// "/password?" + );
// new URLSearchParams({ loginName: values.loginName }) }
// ); } else {
// }
} }
}); });
} }

View File

@@ -42,7 +42,7 @@ export default function VerifyEmailForm({ userId, code, submit }: Props) {
async function submitCode(values: Inputs) { async function submitCode(values: Inputs) {
setLoading(true); setLoading(true);
const res = await fetch("/verifyemail", { const res = await fetch("/api/verifyemail", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -66,7 +66,7 @@ export default function VerifyEmailForm({ userId, code, submit }: Props) {
async function resendCode() { async function resendCode() {
setLoading(true); setLoading(true);
const res = await fetch("/resendverifyemail", { const res = await fetch("/api/resendverifyemail", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@@ -55,9 +55,12 @@ export async function updateSessionCookie(
: [session]; : [session];
const foundIndex = sessions.findIndex((session) => session.id === id); const foundIndex = sessions.findIndex((session) => session.id === id);
sessions[foundIndex] = session; if (foundIndex > -1) {
sessions[foundIndex] = session;
return setSessionHttpOnlyCookie(sessions); return setSessionHttpOnlyCookie(sessions);
} else {
throw "updateSessionCookie: session id now found";
}
} }
export async function removeSessionFromCookie( export async function removeSessionFromCookie(
@@ -119,11 +122,9 @@ export async function getSessionCookieByLoginName(
const cookiesList = cookies(); const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions"); const stringifiedCookie = cookiesList.get("sessions");
console.log("str", stringifiedCookie);
if (stringifiedCookie?.value) { if (stringifiedCookie?.value) {
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
console.log("sessions", sessions);
const found = sessions.find((s) => s.loginName === loginName); const found = sessions.find((s) => s.loginName === loginName);
if (found) { if (found) {
return found; return found;

View File

@@ -86,13 +86,9 @@ export async function setSessionAndUpdateCookie(
loginName: session.factors?.user?.loginName ?? "", loginName: session.factors?.user?.loginName ?? "",
}; };
return updateSessionCookie(sessionCookie.id, newCookie) return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
.then(() => { return session;
return session; });
})
.catch((error) => {
throw "could not set cookie";
});
} else { } else {
throw "could not get session or session does not have loginName"; throw "could not get session or session does not have loginName";
} }

19
pnpm-lock.yaml generated
View File

@@ -59,6 +59,7 @@ importers:
react-dom: 18.2.0 react-dom: 18.2.0
react-hook-form: 7.39.5 react-hook-form: 7.39.5
sass: ^1.62.0 sass: ^1.62.0
swr: ^2.2.0
tailwindcss: 3.2.4 tailwindcss: 3.2.4
tinycolor2: 1.4.2 tinycolor2: 1.4.2
ts-jest: ^29.1.0 ts-jest: ^29.1.0
@@ -84,6 +85,7 @@ importers:
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
react-hook-form: 7.39.5_react@18.2.0 react-hook-form: 7.39.5_react@18.2.0
sass: 1.62.0 sass: 1.62.0
swr: 2.2.0_react@18.2.0
tinycolor2: 1.4.2 tinycolor2: 1.4.2
devDependencies: devDependencies:
'@bufbuild/buf': 1.15.0 '@bufbuild/buf': 1.15.0
@@ -6841,6 +6843,15 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
/swr/2.2.0_react@18.2.0:
resolution: {integrity: sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
use-sync-external-store: 1.2.0_react@18.2.0
dev: false
/symbol-tree/3.2.4: /symbol-tree/3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: true dev: true
@@ -7491,6 +7502,14 @@ packages:
requires-port: 1.0.0 requires-port: 1.0.0
dev: true dev: true
/use-sync-external-store/1.2.0_react@18.2.0:
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
dev: false
/util-deprecate/1.0.2: /util-deprecate/1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}