diff --git a/apps/login/app/(login)/password/page.tsx b/apps/login/app/(login)/password/page.tsx
index c0c86096e8c..c6a36ea1ca3 100644
--- a/apps/login/app/(login)/password/page.tsx
+++ b/apps/login/app/(login)/password/page.tsx
@@ -1,32 +1,51 @@
-"use client";
-import { Button, ButtonVariants } from "#/ui/Button";
-import { TextInput } from "#/ui/Input";
+import { getSession, server } from "#/lib/zitadel";
+import PasswordForm from "#/ui/PasswordForm";
import UserAvatar from "#/ui/UserAvatar";
-import { useRouter } from "next/navigation";
+import { getMostRecentCookieWithLoginname } from "#/utils/cookies";
-export default function Page() {
- const router = useRouter();
+async function loadSession(loginName: string) {
+ const recent = await getMostRecentCookieWithLoginname(loginName);
+ console.log("found recent cookie: ", recent);
+
+ return getSession(server, recent.id, recent.token).then(({ session }) => {
+ console.log(session);
+ return session;
+ });
+ // const res = await fetch(
+ // `http://localhost:3000/session?` +
+ // new URLSearchParams({
+ // loginName: loginName,
+ // }),
+ // {
+ // method: "GET",
+ // headers: {
+ // "Content-Type": "application/json",
+ // },
+ // }
+ // );
+
+ // if (!res.ok) {
+ // throw new Error("Failed to load session");
+ // }
+
+ // return res.json();
+}
+
+export default async function Page({ searchParams }: { searchParams: any }) {
+ const { loginName } = searchParams;
+ console.log(loginName);
+
+ const sessionFactors = await loadSession(loginName);
+ console.log(sessionFactors);
return (
-
Password
+
{sessionFactors.factors.user.displayName}
Enter your password.
-
+
-
-
-
-
-
-
-
-
+
);
}
diff --git a/apps/login/app/(login)/username/page.tsx b/apps/login/app/(login)/username/page.tsx
index a68482ef906..156949dab9c 100644
--- a/apps/login/app/(login)/username/page.tsx
+++ b/apps/login/app/(login)/username/page.tsx
@@ -1,7 +1,4 @@
"use client";
-
-import { Button, ButtonVariants } from "#/ui/Button";
-import IdentityProviders from "#/ui/IdentityProviders";
import UsernameForm from "#/ui/UsernameForm";
export default function Page() {
diff --git a/apps/login/app/session/route.ts b/apps/login/app/session/route.ts
index 0ee1f7a8368..934bdccb1c7 100644
--- a/apps/login/app/session/route.ts
+++ b/apps/login/app/session/route.ts
@@ -1,4 +1,11 @@
-import { createSession, server, setSession } from "#/lib/zitadel";
+import { createSession, getSession, server, setSession } from "#/lib/zitadel";
+import {
+ SessionCookie,
+ addSessionToCookie,
+ getMostRecentCookieWithLoginname,
+ getMostRecentSessionCookie,
+ updateSessionCookie,
+} from "#/utils/cookies";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
@@ -6,21 +13,93 @@ export async function POST(request: NextRequest) {
if (body) {
const { loginName } = body;
- const session = await createSession(server, loginName);
- return NextResponse.json(session);
+ const createdSession = await createSession(server, loginName);
+
+ return getSession(
+ server,
+ createdSession.sessionId,
+ createdSession.sessionToken
+ ).then(({ session }) => {
+ console.log(session);
+ const sessionCookie: SessionCookie = {
+ id: createdSession.sessionId,
+ token: createdSession.sessionToken,
+ changeDate: session.changeDate,
+ loginName: session.factors.user.loginName,
+ };
+ return addSessionToCookie(sessionCookie).then(() => {
+ return NextResponse.json({ factors: session.factors });
+ });
+ });
} else {
return NextResponse.error();
}
}
+/**
+ *
+ * @param request password for the most recent session
+ * @returns the updated most recent Session with the added password
+ */
export async function PUT(request: NextRequest) {
const body = await request.json();
if (body) {
- const { loginName } = body;
+ const { password } = body;
- const session = await setSession(server, loginName);
- return NextResponse.json(session);
+ const recent = await getMostRecentSessionCookie();
+ console.log("found recent cookie: ", recent);
+ const session = await setSession(server, recent.id, recent.token, password);
+ console.log("updatedsession", session);
+
+ const sessionCookie: SessionCookie = {
+ id: recent.id,
+ token: session.sessionToken,
+ changeDate: session.details.changeDate,
+ loginName: recent.loginName,
+ };
+
+ return getSession(server, sessionCookie.id, sessionCookie.token).then(
+ ({ session }) => {
+ console.log(session);
+ const newCookie: SessionCookie = {
+ id: sessionCookie.id,
+ token: sessionCookie.token,
+ changeDate: session.changeDate,
+ loginName: session.factors.user.loginName,
+ };
+ // return addSessionToCookie(sessionCookie).then(() => {
+ // return NextResponse.json({ factors: session.factors });
+ // });
+ return updateSessionCookie(sessionCookie.id, sessionCookie).then(() => {
+ console.log("updatedRecent:", sessionCookie);
+ return NextResponse.json({ factors: session.factors });
+ });
+ }
+ );
} else {
return NextResponse.error();
}
}
+
+// /**
+// *
+// * @param request loginName of a session
+// * @returns the session
+// */
+// export async function GET(request: NextRequest) {
+// console.log(request);
+// if (request) {
+// const { loginName } = request.params;
+
+// const recent = await getMostRecentCookieWithLoginname(loginName);
+// console.log("found recent cookie: ", recent);
+
+// return getSession(server, recent.id, recent.token).then(({ session }) => {
+// console.log(session);
+
+// return NextResponse.json({ factors: session.factors });
+// });
+// } else {
+// return NextResponse.error();
+// }
+// }
diff --git a/apps/login/lib/zitadel.ts b/apps/login/lib/zitadel.ts
index 96476e8e407..6311d3ef6a6 100644
--- a/apps/login/lib/zitadel.ts
+++ b/apps/login/lib/zitadel.ts
@@ -90,10 +90,24 @@ export function createSession(
export function setSession(
server: ZitadelServer,
- loginName: string
+ sessionId: string,
+ sessionToken: string,
+ password: string
): Promise {
const sessionService = session.getSession(server);
- return sessionService.setSession({ checks: { user: { loginName } } }, {});
+ return sessionService.setSession(
+ { sessionId, sessionToken, checks: { password: { password } } },
+ {}
+ );
+}
+
+export function getSession(
+ server: ZitadelServer,
+ sessionId: string,
+ sessionToken: string
+): Promise {
+ const sessionService = session.getSession(server);
+ return sessionService.getSession({ sessionId, sessionToken }, {});
}
export type AddHumanUserData = {
diff --git a/apps/login/next.config.js b/apps/login/next.config.js
index 2f60c3de82e..47304cc9a2d 100755
--- a/apps/login/next.config.js
+++ b/apps/login/next.config.js
@@ -3,8 +3,7 @@ const nextConfig = {
reactStrictMode: true, // Recommended for the `pages` directory, default in `app`.
swcMinify: true,
experimental: {
- // Required:
- appDir: true,
+ serverActions: true,
},
images: {
remotePatterns: [
diff --git a/apps/login/ui/Avatar.tsx b/apps/login/ui/Avatar.tsx
index 7275da2b00f..25778cdc5eb 100644
--- a/apps/login/ui/Avatar.tsx
+++ b/apps/login/ui/Avatar.tsx
@@ -1,4 +1,4 @@
-import { Color, ColorShade, getColorHash } from "#/utils/colors";
+import { ColorShade, getColorHash } from "#/utils/colors";
import { useTheme } from "next-themes";
import { FC } from "react";
@@ -23,7 +23,7 @@ export const Avatar: FC = ({
imageUrl,
shadow,
}) => {
- const { resolvedTheme } = useTheme();
+ // const { resolvedTheme } = useTheme();
let credentials = "";
if (name) {
@@ -46,15 +46,15 @@ export const Avatar: FC = ({
const color: ColorShade = getColorHash(loginName);
- const avatarStyleDark = {
- backgroundColor: color[900],
- color: color[200],
- };
+ // const avatarStyleDark = {
+ // backgroundColor: color[900],
+ // color: color[200],
+ // };
- const avatarStyleLight = {
- backgroundColor: color[200],
- color: color[900],
- };
+ // const avatarStyleLight = {
+ // backgroundColor: color[200],
+ // color: color[900],
+ // };
return (
= ({
? "w-[32px] h-[32px] font-bold"
: ""
}`}
- style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark}
+ // style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark}
>
{imageUrl ? (
![]()
({
mode: "onBlur",
});
@@ -22,7 +22,7 @@ export default function UsernameForm() {
const router = useRouter();
- async function submitUsername(values: Inputs) {
+ async function submitPassword(values: Inputs) {
setLoading(true);
const res = await fetch("/session", {
method: "PUT",
@@ -43,9 +43,10 @@ export default function UsernameForm() {
return res.json();
}
- function submitAndLink(value: Inputs): Promise
{
- return submitUsername(value).then((resp: any) => {
- return router.push(`/password`);
+ function submitPasswordAndContinue(value: Inputs): Promise {
+ console.log(value);
+ return submitPassword(value).then((resp: any) => {
+ return router.push(`/success`);
});
}
@@ -58,7 +59,7 @@ export default function UsernameForm() {
type="password"
autoComplete="password"
{...register("password", { required: "This field is required" })}
- label="Loginname"
+ label="Password"
// error={errors.username?.message as string}
/>
@@ -73,7 +74,7 @@ export default function UsernameForm() {
className="self-end"
variant={ButtonVariants.Primary}
disabled={loading || !formState.isValid}
- onClick={handleSubmit(submitAndLink)}
+ onClick={handleSubmit(submitPasswordAndContinue)}
>
{loading && }
continue
diff --git a/apps/login/ui/UserAvatar.tsx b/apps/login/ui/UserAvatar.tsx
index fbf3b3886e5..d64679367f5 100644
--- a/apps/login/ui/UserAvatar.tsx
+++ b/apps/login/ui/UserAvatar.tsx
@@ -1,16 +1,32 @@
import { Avatar, AvatarSize } from "#/ui/Avatar";
+import { ChevronDownIcon } from "@heroicons/react/24/outline";
+import Link from "next/link";
type Props = {
- name: string;
+ loginName: string;
+ showDropdown: boolean;
};
-export default function UserAvatar({ name }: Props) {
+export default function UserAvatar({ loginName, showDropdown }: Props) {
return (
-
{name}
+
{loginName}
+
+ {showDropdown && (
+
+
+
+ )}
);
}
diff --git a/apps/login/ui/UsernameForm.tsx b/apps/login/ui/UsernameForm.tsx
index fe05c8b8c4e..4c2d9cf1b55 100644
--- a/apps/login/ui/UsernameForm.tsx
+++ b/apps/login/ui/UsernameForm.tsx
@@ -41,9 +41,10 @@ export default function UsernameForm() {
return res.json();
}
- function submitAndLink(value: Inputs): Promise {
- return submitUsername(value).then((resp: any) => {
- return router.push(`/password`);
+ function submitUsernameAndContinue(value: Inputs): Promise {
+ return submitUsername(value).then(({ factors }) => {
+ console.log(factors);
+ return router.push(`/password?loginName=${factors.user.loginName}`);
});
}
@@ -71,7 +72,7 @@ export default function UsernameForm() {
className="self-end"
variant={ButtonVariants.Primary}
disabled={loading || !formState.isValid}
- onClick={handleSubmit(submitAndLink)}
+ onClick={handleSubmit(submitUsernameAndContinue)}
>
{loading && }
continue
diff --git a/apps/login/utils/cookies.ts b/apps/login/utils/cookies.ts
new file mode 100644
index 00000000000..3dad52bb67e
--- /dev/null
+++ b/apps/login/utils/cookies.ts
@@ -0,0 +1,152 @@
+"use server";
+
+import { cookies } from "next/headers";
+
+export type SessionCookie = {
+ id: string;
+ token: string;
+ loginName: string;
+ changeDate: string;
+};
+
+async function set(sessions: SessionCookie[]) {
+ const cookiesList = cookies();
+ // @ts-ignore
+ cookiesList.set({
+ name: "sessions",
+ value: JSON.stringify(sessions),
+ httpOnly: true,
+ path: "/",
+ });
+}
+
+export async function addSessionToCookie(session: SessionCookie): Promise {
+ const cookiesList = cookies();
+ // const hasSessions = cookiesList.has("sessions");
+ // if (hasSessions) {
+ const stringifiedCookie = cookiesList.get("sessions");
+
+ const currentSessions: SessionCookie[] = stringifiedCookie?.value
+ ? JSON.parse(stringifiedCookie?.value)
+ : [];
+
+ // @ts-ignore
+ return cookiesList.set({
+ name: "sessions",
+ value: JSON.stringify([...currentSessions, session]),
+ httpOnly: true,
+ path: "/",
+ });
+ // } else {
+ // return set([session]);
+ // }
+}
+
+export async function updateSessionCookie(
+ id: string,
+ session: SessionCookie
+): Promise {
+ const cookiesList = cookies();
+ // const hasSessions = cookiesList.has("sessions");
+ // if (hasSessions) {
+ const stringifiedCookie = cookiesList.get("sessions");
+
+ const sessions: SessionCookie[] = stringifiedCookie?.value
+ ? JSON.parse(stringifiedCookie?.value)
+ : [session];
+
+ const foundIndex = sessions.findIndex((session) => session.id === id);
+ sessions[foundIndex] = session;
+
+ // @ts-ignore
+ return cookiesList.set({
+ name: "sessions",
+ value: JSON.stringify(sessions),
+ httpOnly: true,
+ path: "/",
+ });
+ // } else {
+ // return Promise.reject();
+ // }
+}
+
+export async function removeSessionFromCookie(
+ session: SessionCookie
+): Promise {
+ const cookiesList = cookies();
+ // const hasSessions = cookiesList.has("sessions");
+ // if (hasSessions) {
+ const stringifiedCookie = cookiesList.get("sessions");
+
+ const sessions: SessionCookie[] = stringifiedCookie?.value
+ ? JSON.parse(stringifiedCookie?.value)
+ : [session];
+
+ const filteredSessions = sessions.filter(
+ (session) => session.id !== session.id
+ );
+
+ // @ts-ignore
+ return cookiesList.set({
+ name: "sessions",
+ value: JSON.stringify(filteredSessions),
+ httpOnly: true,
+ path: "/",
+ });
+ // } else {
+ // return Promise.reject();
+ // }
+}
+
+export async function getMostRecentSessionCookie(): Promise {
+ const cookiesList = cookies();
+ // const hasSessions = cookiesList.has("sessions");
+ // if (hasSessions) {
+ const stringifiedCookie = cookiesList.get("sessions");
+
+ if (stringifiedCookie?.value) {
+ const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
+
+ console.log(sessions);
+ const latest = sessions.reduce((prev, current) => {
+ return new Date(prev.changeDate).getTime() >
+ new Date(current.changeDate).getTime()
+ ? prev
+ : current;
+ });
+
+ return latest;
+ } else {
+ return Promise.reject();
+ }
+ // } else {
+ // return Promise.reject();
+ // }
+}
+
+export async function getMostRecentCookieWithLoginname(
+ loginName: string
+): Promise {
+ const cookiesList = cookies();
+
+ const stringifiedCookie = cookiesList.get("sessions");
+
+ if (stringifiedCookie?.value) {
+ const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
+
+ const latest = sessions
+ .filter((cookie) => cookie.loginName === loginName)
+ .reduce((prev, current) => {
+ return new Date(prev.changeDate).getTime() >
+ new Date(current.changeDate).getTime()
+ ? prev
+ : current;
+ });
+
+ return latest;
+ } else {
+ return Promise.reject();
+ }
+}
+
+export async function clearSessions() {}
diff --git a/packages/zitadel-server/src/v2/session/session.ts b/packages/zitadel-server/src/v2/session/session.ts
index 493cf4091a6..4569fa4db8d 100644
--- a/packages/zitadel-server/src/v2/session/session.ts
+++ b/packages/zitadel-server/src/v2/session/session.ts
@@ -8,7 +8,6 @@ import {
import { ZitadelServer, createClient, getServers } from "../../server";
export const getSession = (server?: string | ZitadelServer) => {
- console.log("init session");
let config;
if (server && typeof server === "string") {
const apps = getServers();