/login route, extend session cookie for authRequestId

This commit is contained in:
peintnermax
2023-08-21 15:14:12 +02:00
parent 26080d0f75
commit e301f6a198
10 changed files with 174 additions and 42 deletions

View File

@@ -1,13 +1,43 @@
import { getAuthRequest, listSessions, server } from "#/lib/zitadel";
import { getAllSessionIds } from "#/utils/cookies";
import { Session } from "@zitadel/server";
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
return NextResponse.json({ found: true });
async function loadSessions(): Promise<Session[]> {
const ids: string[] = await getAllSessionIds();
if (ids && ids.length) {
const response = await listSessions(
server,
ids.filter((id: string | undefined) => !!id)
);
return response?.sessions ?? [];
} else {
console.info("No session cookie found.");
return [];
}
}
export async function POST(request: NextRequest) {
const body = await request.json();
if (body) {
return NextResponse.json({ found: true });
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const authRequestId = searchParams.get("authRequest");
if (authRequestId) {
const response = await getAuthRequest(server, { authRequestId });
const sessions = await loadSessions();
if (sessions.length) {
return NextResponse.json(sessions);
} else {
const loginNameUrl = new URL("/loginname", request.url);
if (response.authRequest?.id) {
loginNameUrl.searchParams.set(
"authRequestId",
response.authRequest?.id
);
}
return NextResponse.redirect(loginNameUrl);
}
} else {
return NextResponse.error();
}

View File

@@ -7,6 +7,7 @@ export default async function Page({
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const loginName = searchParams?.loginName;
const authRequestId = searchParams?.authRequestId;
const submit: boolean = searchParams?.submit === "true";
const loginSettings = await getLoginSettings(server);
@@ -19,6 +20,7 @@ export default async function Page({
<UsernameForm
loginSettings={loginSettings}
loginName={loginName}
authRequestId={authRequestId}
submit={submit}
/>
</div>

View File

@@ -7,47 +7,53 @@ import { getSessionCookieById } from "#/utils/cookies";
import { createSessionAndUpdateCookie } from "#/utils/session";
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const sessionId = searchParams.get("sessionId");
if (sessionId) {
const sessionCookie = await getSessionCookieById(sessionId);
// export async function GET(request: NextRequest) {
// const { searchParams } = new URL(request.url);
// const sessionId = searchParams.get("sessionId");
// if (sessionId) {
// const sessionCookie = await getSessionCookieById(sessionId);
const session = await getSession(
server,
sessionCookie.id,
sessionCookie.token
);
// const session = await getSession(
// server,
// sessionCookie.id,
// sessionCookie.token
// );
const userId = session?.session?.factors?.user?.id;
// const userId = session?.session?.factors?.user?.id;
if (userId) {
return listAuthenticationMethodTypes(userId)
.then((methods) => {
return NextResponse.json(methods);
})
.catch((error) => {
return NextResponse.json(error, { status: 500 });
});
} else {
return NextResponse.json(
{ details: "could not get session" },
{ status: 500 }
);
}
} else {
return NextResponse.json({}, { status: 400 });
}
}
// if (userId) {
// return listAuthenticationMethodTypes(userId)
// .then((methods) => {
// return NextResponse.json(methods);
// })
// .catch((error) => {
// return NextResponse.json(error, { status: 500 });
// });
// } else {
// return NextResponse.json(
// { details: "could not get session" },
// { status: 500 }
// );
// }
// } else {
// return NextResponse.json({}, { status: 400 });
// }
// }
export async function POST(request: NextRequest) {
const body = await request.json();
if (body) {
const { loginName } = body;
const { loginName, authRequestId } = body;
const domain: string = request.nextUrl.hostname;
return createSessionAndUpdateCookie(loginName, undefined, domain, undefined)
return createSessionAndUpdateCookie(
loginName,
undefined,
domain,
undefined,
authRequestId
)
.then((session) => {
if (session.factors?.user?.id) {
return listAuthenticationMethodTypes(session.factors?.user?.id)

View File

@@ -2,6 +2,7 @@ import {
ZitadelServer,
ZitadelServerOptions,
user,
oidc,
settings,
getServers,
initializeServer,
@@ -29,6 +30,10 @@ import {
StartIdentityProviderFlowResponse,
RetrieveIdentityProviderInformationRequest,
RetrieveIdentityProviderInformationResponse,
GetAuthRequestResponse,
GetAuthRequestRequest,
CreateCallbackRequest,
CreateCallbackResponse,
} from "@zitadel/server";
export const zitadelConfig: ZitadelServerOptions = {
@@ -224,6 +229,28 @@ export async function retrieveIdentityProviderInformation(
});
}
export async function getAuthRequest(
server: ZitadelServer,
{ authRequestId }: GetAuthRequestRequest
): Promise<GetAuthRequestResponse> {
const oidcService = oidc.getOidc(server);
return oidcService.getAuthRequest({
authRequestId,
});
}
export async function createCallback(
server: ZitadelServer,
{ authRequestId }: CreateCallbackRequest
): Promise<CreateCallbackResponse> {
const oidcService = oidc.getOidc(server);
return oidcService.createCallback({
authRequestId,
});
}
export async function verifyEmail(
server: ZitadelServer,
userId: string,

View File

@@ -15,12 +15,14 @@ type Inputs = {
type Props = {
loginSettings: LoginSettings | undefined;
loginName: string | undefined;
authRequestId: string | undefined;
submit: boolean;
};
export default function UsernameForm({
loginSettings,
loginName,
authRequestId,
submit,
}: Props) {
const { register, handleSubmit, formState } = useForm<Inputs>({
@@ -37,14 +39,17 @@ export default function UsernameForm({
async function submitLoginName(values: Inputs) {
setLoading(true);
const body = {
loginName: values.loginName,
};
const res = await fetch("/api/loginname", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
loginName: values.loginName,
}),
body: JSON.stringify(authRequestId ? { ...body, authRequestId } : body),
});
setLoading(false);

View File

@@ -7,6 +7,7 @@ export type SessionCookie = {
token: string;
loginName: string;
changeDate: string;
authRequestId?: string; // if its linked to an OIDC flow
};
function setSessionHttpOnlyCookie(sessions: SessionCookie[]) {
@@ -146,6 +147,18 @@ export async function getAllSessionIds(): Promise<any> {
}
}
export async function getAllSessions(): Promise<SessionCookie[]> {
const cookiesList = cookies();
const stringifiedCookie = cookiesList.get("sessions");
if (stringifiedCookie?.value) {
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
return sessions;
} else {
return [];
}
}
/**
* Returns most recent session filtered by optinal loginName
* @param loginName

View File

@@ -10,7 +10,8 @@ export async function createSessionAndUpdateCookie(
loginName: string,
password: string | undefined,
domain: string,
challenges: ChallengeKind[] | undefined
challenges: ChallengeKind[] | undefined,
authRequestId: string | undefined
): Promise<Session> {
const createdSession = await createSession(
server,
@@ -34,6 +35,10 @@ export async function createSessionAndUpdateCookie(
loginName: response.session?.factors?.user?.loginName ?? "",
};
if (authRequestId) {
sessionCookie.authRequestId = authRequestId;
}
return addSessionToCookie(sessionCookie).then(() => {
return response.session as Session;
});
@@ -57,7 +62,8 @@ export async function setSessionAndUpdateCookie(
password: string | undefined,
passkey: { credentialAssertionData: any } | undefined,
domain: string | undefined,
challenges: ChallengeKind[] | undefined
challenges: ChallengeKind[] | undefined,
authRequestId: string | undefined
): Promise<SessionWithChallenges> {
return setSession(
server,
@@ -76,6 +82,10 @@ export async function setSessionAndUpdateCookie(
loginName: loginName,
};
if (authRequestId) {
sessionCookie.authRequestId = authRequestId;
}
return getSession(server, sessionCookie.id, sessionCookie.token).then(
(response) => {
if (response?.session && response.session.factors?.user?.loginName) {
@@ -87,6 +97,10 @@ export async function setSessionAndUpdateCookie(
loginName: session.factors?.user?.loginName ?? "",
};
if (sessionCookie.authRequestId) {
newCookie.authRequestId = sessionCookie.authRequestId;
}
return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
return { challenges: updatedSession.challenges, ...session };
});

View File

@@ -1,6 +1,7 @@
import * as settings from "./v2/settings";
import * as session from "./v2/session";
import * as user from "./v2/user";
import * as oidc from "./v2/oidc";
import * as management from "./management";
import * as login from "./proto/server/zitadel/settings/v2alpha/login_settings";
@@ -24,6 +25,13 @@ export {
Challenges_Passkey,
} from "./proto/server/zitadel/session/v2alpha/challenge";
export {
GetAuthRequestRequest,
GetAuthRequestResponse,
CreateCallbackRequest,
CreateCallbackResponse,
} from "./proto/server/zitadel/oidc/v2alpha/oidc_service";
export {
Session,
Factors,
@@ -96,4 +104,5 @@ export {
login,
password,
legal,
oidc,
};

View File

@@ -0,0 +1,2 @@
export * from "./oidc";
export * from "../../proto/server/zitadel/oidc/v2alpha/oidc_service";

View File

@@ -0,0 +1,24 @@
import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions";
import { ZitadelServer, createClient, getServers } from "../../server";
import { OIDCServiceClient, OIDCServiceDefinition } from ".";
export const getOidc = (server?: string | ZitadelServer) => {
let config;
if (server && typeof server === "string") {
const apps = getServers();
config = apps.find((a) => a.name === server)?.config;
} else if (server && typeof server === "object") {
config = server.config;
}
if (!config) {
throw Error("No ZITADEL server found");
}
return createClient<OIDCServiceClient>(
OIDCServiceDefinition as CompatServiceDefinition,
config.apiUrl,
config.token
);
};