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

View File

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

View File

@@ -1,24 +1,48 @@
"use client";
import { useState } from "react";
import { useEffect, useState } from "react";
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 { Button, ButtonVariants } from "./Button";
import Alert from "./Alert";
import { Spinner } from "./Spinner";
type Props = {
loginName: string;
challenge: Challenges_Passkey;
};
export default function LoginPasskey({ challenge }: Props) {
export default function LoginPasskey({ loginName, challenge }: Props) {
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
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(
passkeyId: string,
passkeyName: string,
@@ -26,7 +50,7 @@ export default function LoginPasskey({ challenge }: Props) {
sessionId: string
) {
setLoading(true);
const res = await fetch("/passkeys/verify", {
const res = await fetch("/api/passkeys/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -99,13 +123,12 @@ export default function LoginPasskey({ challenge }: Props) {
}
return (
<form className="w-full">
<div className="w-full">
{error && (
<div className="py-4">
<Alert>{error}</Alert>
</div>
)}
<div className="mt-8 flex w-full flex-row items-center">
<Button
type="button"
@@ -127,6 +150,6 @@ export default function LoginPasskey({ challenge }: Props) {
continue
</Button>
</div>
</form>
</div>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -86,13 +86,9 @@ export async function setSessionAndUpdateCookie(
loginName: session.factors?.user?.loginName ?? "",
};
return updateSessionCookie(sessionCookie.id, newCookie)
.then(() => {
return session;
})
.catch((error) => {
throw "could not set cookie";
});
return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
return session;
});
} else {
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-hook-form: 7.39.5
sass: ^1.62.0
swr: ^2.2.0
tailwindcss: 3.2.4
tinycolor2: 1.4.2
ts-jest: ^29.1.0
@@ -84,6 +85,7 @@ importers:
react-dom: 18.2.0_react@18.2.0
react-hook-form: 7.39.5_react@18.2.0
sass: 1.62.0
swr: 2.2.0_react@18.2.0
tinycolor2: 1.4.2
devDependencies:
'@bufbuild/buf': 1.15.0
@@ -6841,6 +6843,15 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
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:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: true
@@ -7491,6 +7502,14 @@ packages:
requires-port: 1.0.0
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:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}