cookie utils

This commit is contained in:
peintnermax
2024-08-08 08:58:54 +02:00
parent 9d5751ae7a
commit 451385c198
22 changed files with 42 additions and 323 deletions

View File

@@ -1,5 +1,5 @@
import { getBrandingSettings, listSessions } from "@/lib/zitadel";
import { getAllSessionCookieIds } from "@/utils/cookies";
import { getAllSessionCookieIds } from "@zitadel/next";
import { UserPlusIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import SessionsList from "@/ui/SessionsList";

View File

@@ -11,7 +11,7 @@ import UserAvatar from "@/ui/UserAvatar";
import {
getMostRecentCookieWithLoginname,
getSessionCookieById,
} from "@/utils/cookies";
} from "@zitadel/next";
export default async function Page({
searchParams,

View File

@@ -13,7 +13,7 @@ import UserAvatar from "@/ui/UserAvatar";
import {
getMostRecentCookieWithLoginname,
getSessionCookieById,
} from "@/utils/cookies";
} from "@zitadel/next";
export default async function Page({
searchParams,

View File

@@ -2,17 +2,14 @@ import {
addOTPEmail,
addOTPSMS,
getBrandingSettings,
getSession,
registerTOTP,
} from "@/lib/zitadel";
import Alert from "@/ui/Alert";
import BackButton from "@/ui/BackButton";
import { Button, ButtonVariants } from "@/ui/Button";
import DynamicTheme from "@/ui/DynamicTheme";
import { Spinner } from "@/ui/Spinner";
import TOTPRegister from "@/ui/TOTPRegister";
import UserAvatar from "@/ui/UserAvatar";
import { getMostRecentCookieWithLoginname } from "@/utils/cookies";
import Link from "next/link";
import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { loadMostRecentSession } from "@zitadel/next";

View File

@@ -3,7 +3,7 @@ import Alert, { AlertType } from "@/ui/Alert";
import DynamicTheme from "@/ui/DynamicTheme";
import RegisterPasskey from "@/ui/RegisterPasskey";
import UserAvatar from "@/ui/UserAvatar";
import { getMostRecentCookieWithLoginname } from "@/utils/cookies";
import { getMostRecentCookieWithLoginname } from "@zitadel/next";
import { loadMostRecentSession } from "@zitadel/next";
export default async function Page({

View File

@@ -6,7 +6,7 @@ import UserAvatar from "@/ui/UserAvatar";
import {
getMostRecentCookieWithLoginname,
getSessionCookieById,
} from "@/utils/cookies";
} from "@zitadel/next";
const title = "Authenticate with a passkey";
const description =

View File

@@ -7,7 +7,7 @@ import Alert from "@/ui/Alert";
import DynamicTheme from "@/ui/DynamicTheme";
import PasswordForm from "@/ui/PasswordForm";
import UserAvatar from "@/ui/UserAvatar";
import { getMostRecentCookieWithLoginname } from "@/utils/cookies";
import { getMostRecentCookieWithLoginname } from "@zitadel/next";
import { loadMostRecentSession } from "@zitadel/next";
export default async function Page({

View File

@@ -1,7 +1,7 @@
import { createCallback, getBrandingSettings, getSession } from "@/lib/zitadel";
import DynamicTheme from "@/ui/DynamicTheme";
import UserAvatar from "@/ui/UserAvatar";
import { getMostRecentCookieWithLoginname } from "@/utils/cookies";
import { getMostRecentCookieWithLoginname } from "@zitadel/next";
import { redirect } from "next/navigation";
async function loadSession(loginName: string, authRequestId?: string) {

View File

@@ -10,7 +10,7 @@ import UserAvatar from "@/ui/UserAvatar";
import {
getMostRecentCookieWithLoginname,
getSessionCookieById,
} from "@/utils/cookies";
} from "@zitadel/next";
export default async function Page({
searchParams,

View File

@@ -4,7 +4,7 @@ import DynamicTheme from "@/ui/DynamicTheme";
import RegisterPasskey from "@/ui/RegisterPasskey";
import RegisterU2F from "@/ui/RegisterU2F";
import UserAvatar from "@/ui/UserAvatar";
import { getMostRecentCookieWithLoginname } from "@/utils/cookies";
import { getMostRecentCookieWithLoginname } from "@zitadel/next";
import { loadMostRecentSession } from "@zitadel/next";
export default async function Page({

View File

@@ -1,9 +1,8 @@
import {
SessionCookie,
getMostRecentSessionCookie,
getSessionCookieById,
getSessionCookieByLoginName,
} from "@/utils/cookies";
} from "@zitadel/next";
import { setSessionAndUpdateCookie } from "@/utils/session";
import { NextRequest, NextResponse, userAgent } from "next/server";
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
@@ -16,7 +15,7 @@ export async function POST(request: NextRequest) {
const { loginName, sessionId, organization, authRequestId, code, method } =
body;
const recentPromise: Promise<SessionCookie> = sessionId
const recentPromise = sessionId
? getSessionCookieById(sessionId).catch((error) => {
return Promise.reject(error);
})

View File

@@ -3,7 +3,7 @@ import {
getSession,
registerPasskey,
} from "@/lib/zitadel";
import { getSessionCookieById } from "@/utils/cookies";
import { getSessionCookieById } from "@zitadel/next";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {

View File

@@ -1,5 +1,5 @@
import { getSession, verifyPasskeyRegistration } from "@/lib/zitadel";
import { getSessionCookieById } from "@/utils/cookies";
import { getSessionCookieById } from "@zitadel/next";
import { NextRequest, NextResponse, userAgent } from "next/server";
export async function POST(request: NextRequest) {

View File

@@ -5,12 +5,11 @@ import {
listAuthenticationMethodTypes,
} from "@/lib/zitadel";
import {
SessionCookie,
getMostRecentSessionCookie,
getSessionCookieById,
getSessionCookieByLoginName,
removeSessionFromCookie,
} from "@/utils/cookies";
} from "@zitadel/next";
import {
createSessionAndUpdateCookie,
createSessionForIdpAndUpdateCookie,
@@ -76,7 +75,7 @@ export async function PUT(request: NextRequest) {
challenges,
} = body;
const recentPromise: Promise<SessionCookie> = sessionId
const recentPromise = sessionId
? getSessionCookieById(sessionId).catch((error) => {
return Promise.reject(error);
})

View File

@@ -4,7 +4,7 @@ import {
registerPasskey,
registerU2F,
} from "@/lib/zitadel";
import { getSessionCookieById } from "@/utils/cookies";
import { getSessionCookieById } from "@zitadel/next";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {

View File

@@ -1,5 +1,5 @@
import { getSession, verifyU2FRegistration } from "@/lib/zitadel";
import { getSessionCookieById } from "@/utils/cookies";
import { getSessionCookieById } from "@zitadel/next";
import { NextRequest, NextResponse, userAgent } from "next/server";
import { VerifyU2FRegistrationRequest } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { PlainMessage } from "@zitadel/client";

View File

@@ -6,7 +6,7 @@ import {
listSessions,
startIdentityProviderFlow,
} from "@/lib/zitadel";
import { SessionCookie, getAllSessions } from "@/utils/cookies";
import { getAllSessions } from "@zitadel/next";
import { NextRequest, NextResponse } from "next/server";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import {
@@ -52,7 +52,7 @@ export async function GET(request: NextRequest) {
const authRequestId = searchParams.get("authRequest");
const sessionId = searchParams.get("sessionId");
const sessionCookies: SessionCookie[] = await getAllSessions();
const sessionCookies = await getAllSessions();
const ids = sessionCookies.map((s) => s.id);
let sessions: Session[] = [];
if (ids && ids.length) {

View File

@@ -1,5 +1,5 @@
import { listSessions } from "@/lib/zitadel";
import { SessionCookie, getAllSessions } from "@/utils/cookies";
import { getAllSessions } from "@zitadel/next";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import { NextRequest, NextResponse } from "next/server";
@@ -12,7 +12,7 @@ async function loadSessions(ids: string[]): Promise<Session[]> {
}
export async function GET(request: NextRequest) {
const sessionCookies: SessionCookie[] = await getAllSessions();
const sessionCookies = await getAllSessions();
const ids = sessionCookies.map((s) => s.id);
let sessions: Session[] = [];
if (ids && ids.length) {

View File

@@ -1,6 +1,6 @@
"use server";
import { getMostRecentCookieWithLoginname } from "@/utils/cookies";
import { getMostRecentCookieWithLoginname } from "@zitadel/next";
import { getSession, verifyTOTPRegistration } from "./zitadel";
export async function verifyTOTP(

View File

@@ -1,284 +0,0 @@
"use server";
import { cookies } from "next/headers";
export type SessionCookie = {
id: string;
token: string;
loginName: string;
organization?: string;
creationDate: string;
expirationDate: string;
changeDate: string;
authRequestId?: string; // if its linked to an OIDC flow
};
function setSessionHttpOnlyCookie(sessions: SessionCookie[]) {
const cookiesList = cookies();
// @ts-ignore
return cookiesList.set({
name: "sessions",
value: JSON.stringify(sessions),
httpOnly: true,
path: "/",
});
}
export async function addSessionToCookie(
session: SessionCookie,
cleanup: boolean = false,
): Promise<any> {
const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions");
let currentSessions: SessionCookie[] = stringifiedCookie?.value
? JSON.parse(stringifiedCookie?.value)
: [];
const index = currentSessions.findIndex(
(s) => s.loginName === session.loginName,
);
if (index > -1) {
currentSessions[index] = session;
} else {
currentSessions = [...currentSessions, session];
}
if (cleanup) {
const now = new Date();
const filteredSessions = currentSessions.filter((session) =>
session.expirationDate ? new Date(session.expirationDate) > now : true,
);
return setSessionHttpOnlyCookie(filteredSessions);
} else {
return setSessionHttpOnlyCookie(currentSessions);
}
}
export async function updateSessionCookie(
id: string,
session: SessionCookie,
cleanup: boolean = false,
): Promise<any> {
const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions");
const sessions: SessionCookie[] = stringifiedCookie?.value
? JSON.parse(stringifiedCookie?.value)
: [session];
const foundIndex = sessions.findIndex((session) => session.id === id);
if (foundIndex > -1) {
sessions[foundIndex] = session;
if (cleanup) {
const now = new Date();
const filteredSessions = sessions.filter((session) =>
session.expirationDate ? new Date(session.expirationDate) > now : true,
);
return setSessionHttpOnlyCookie(filteredSessions);
} else {
return setSessionHttpOnlyCookie(sessions);
}
} else {
throw "updateSessionCookie: session id now found";
}
}
export async function removeSessionFromCookie(
session: SessionCookie,
cleanup: boolean = false,
): Promise<any> {
const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions");
const sessions: SessionCookie[] = stringifiedCookie?.value
? JSON.parse(stringifiedCookie?.value)
: [session];
const reducedSessions = sessions.filter((s) => s.id !== session.id);
if (cleanup) {
const now = new Date();
const filteredSessions = reducedSessions.filter((session) =>
session.expirationDate ? new Date(session.expirationDate) > now : true,
);
return setSessionHttpOnlyCookie(filteredSessions);
} else {
return setSessionHttpOnlyCookie(reducedSessions);
}
}
export async function getMostRecentSessionCookie(): Promise<any> {
const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions");
if (stringifiedCookie?.value) {
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
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("no session cookie found");
}
}
export async function getSessionCookieById(
id: string,
organization?: string,
): Promise<SessionCookie> {
const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions");
if (stringifiedCookie?.value) {
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
const found = sessions.find((s) =>
organization
? s.organization === organization && s.id === id
: s.id === id,
);
if (found) {
return found;
} else {
return Promise.reject();
}
} else {
return Promise.reject();
}
}
export async function getSessionCookieByLoginName(
loginName: string,
organization?: string,
): Promise<SessionCookie> {
const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions");
if (stringifiedCookie?.value) {
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
const found = sessions.find((s) =>
organization
? s.organization === organization && s.loginName === loginName
: s.loginName === loginName,
);
if (found) {
return found;
} else {
return Promise.reject("no cookie found with loginName: " + loginName);
}
} else {
return Promise.reject("no session cookie found");
}
}
/**
*
* @param cleanup when true, removes all expired sessions, default true
* @returns Session Cookies
*/
export async function getAllSessionCookieIds(
cleanup: boolean = false,
): Promise<any> {
const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions");
if (stringifiedCookie?.value) {
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
if (cleanup) {
const now = new Date();
return sessions
.filter((session) =>
session.expirationDate
? new Date(session.expirationDate) > now
: true,
)
.map((session) => session.id);
} else {
return sessions.map((session) => session.id);
}
} else {
return [];
}
}
/**
*
* @param cleanup when true, removes all expired sessions, default true
* @returns Session Cookies
*/
export async function getAllSessions(
cleanup: boolean = false,
): Promise<SessionCookie[]> {
const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions");
if (stringifiedCookie?.value) {
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
if (cleanup) {
const now = new Date();
return sessions.filter((session) =>
session.expirationDate ? new Date(session.expirationDate) > now : true,
);
} else {
return sessions;
}
} else {
return [];
}
}
/**
* Returns most recent session filtered by optinal loginName
* @param loginName
* @returns most recent session
*/
export async function getMostRecentCookieWithLoginname(
loginName?: string,
organization?: string,
): Promise<any> {
const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions");
if (stringifiedCookie?.value) {
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
let filtered = sessions.filter((cookie) => {
return !!loginName ? cookie.loginName === loginName : true;
});
if (organization) {
filtered = filtered.filter((cookie) => {
return cookie.organization === organization;
});
}
const latest =
filtered && filtered.length
? filtered.reduce((prev, current) => {
return new Date(prev.changeDate).getTime() >
new Date(current.changeDate).getTime()
? prev
: current;
})
: undefined;
if (latest) {
return latest;
} else {
console.error("sessions", sessions, loginName, organization);
return Promise.reject("Could not get the context or retrieve a session");
}
} else {
return Promise.reject("Could not read session cookie");
}
}
export async function clearSessions() {}

View File

@@ -6,11 +6,7 @@ import {
getSession,
setSession,
} from "@/lib/zitadel";
import {
SessionCookie,
addSessionToCookie,
updateSessionCookie,
} from "./cookies";
import { addSessionToCookie, updateSessionCookie } from "@zitadel/next";
import {
Challenges,
RequestChallenges,
@@ -19,6 +15,17 @@ import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { PlainMessage } from "@zitadel/client";
type CustomCookieData = {
id: string;
token: string;
loginName: string;
organization?: string;
creationDate: string;
expirationDate: string;
changeDate: string;
authRequestId?: string; // if its linked to an OIDC flow
};
export async function createSessionAndUpdateCookie(
loginName: string,
password: string | undefined,
@@ -43,7 +50,7 @@ export async function createSessionAndUpdateCookie(
createdSession.sessionToken,
).then((response) => {
if (response?.session && response.session?.factors?.user?.loginName) {
const sessionCookie: SessionCookie = {
const sessionCookie: any = {
id: createdSession.sessionId,
token: createdSession.sessionToken,
creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`,
@@ -61,7 +68,7 @@ export async function createSessionAndUpdateCookie(
sessionCookie.organization = organization;
}
return addSessionToCookie(sessionCookie).then(() => {
return addSessionToCookie<CustomCookieData>(sessionCookie).then(() => {
return response.session as Session;
});
} else {
@@ -96,7 +103,7 @@ export async function createSessionForUserIdAndUpdateCookie(
createdSession.sessionToken,
).then((response) => {
if (response?.session && response.session?.factors?.user?.loginName) {
const sessionCookie: SessionCookie = {
const sessionCookie: any = {
id: createdSession.sessionId,
token: createdSession.sessionToken,
creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`,
@@ -146,7 +153,7 @@ export async function createSessionForIdpAndUpdateCookie(
createdSession.sessionToken,
).then((response) => {
if (response?.session && response.session?.factors?.user?.loginName) {
const sessionCookie: SessionCookie = {
const sessionCookie: any = {
id: createdSession.sessionId,
token: createdSession.sessionToken,
creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`,
@@ -181,7 +188,7 @@ export type SessionWithChallenges = Session & {
};
export async function setSessionAndUpdateCookie(
recentCookie: SessionCookie,
recentCookie: CustomCookieData,
checks: PlainMessage<Checks>,
challenges: RequestChallenges | undefined,
authRequestId: string | undefined,
@@ -193,7 +200,7 @@ export async function setSessionAndUpdateCookie(
checks,
).then((updatedSession) => {
if (updatedSession) {
const sessionCookie: SessionCookie = {
const sessionCookie: CustomCookieData = {
id: recentCookie.id,
token: updatedSession.sessionToken,
creationDate: recentCookie.creationDate,
@@ -211,7 +218,7 @@ export async function setSessionAndUpdateCookie(
(response) => {
if (response?.session && response.session.factors?.user?.loginName) {
const { session } = response;
const newCookie: SessionCookie = {
const newCookie: CustomCookieData = {
id: sessionCookie.id,
token: updatedSession.sessionToken,
creationDate: sessionCookie.creationDate,

View File

@@ -1,4 +1,5 @@
// import "./styles.css";
export { ZitadelNextProvider, type ZitadelNextProps } from "./components/ZitadelNextProvider";
export * from "./utils/cookies";
export { loadMostRecentSession } from "./utils/session";