diff --git a/apps/login/app/(login)/loginname/page.tsx b/apps/login/app/(login)/loginname/page.tsx index 3a80fc3b7c9..8704926a33c 100644 --- a/apps/login/app/(login)/loginname/page.tsx +++ b/apps/login/app/(login)/loginname/page.tsx @@ -40,8 +40,7 @@ export default async function Page({ const loginSettings = await getLoginSettings(server, organization); const legal = await getLegalAndSupportSettings(server); - // TODO if org idps should be shown replace emptystring with the orgId. - const identityProviders = await getIdentityProviders(server, ""); + const identityProviders = await getIdentityProviders(server, organization); const host = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` diff --git a/apps/login/app/(login)/passkey/add/page.tsx b/apps/login/app/(login)/passkey/add/page.tsx index 15237a2d4de..09a18e2ba07 100644 --- a/apps/login/app/(login)/passkey/add/page.tsx +++ b/apps/login/app/(login)/passkey/add/page.tsx @@ -9,7 +9,7 @@ export default async function Page({ }: { searchParams: Record; }) { - const { loginName, prompt } = searchParams; + const { loginName, prompt, organization } = searchParams; const sessionFactors = await loadSession(loginName); diff --git a/apps/login/app/(login)/password/page.tsx b/apps/login/app/(login)/password/page.tsx index 5458909be97..014f3f07982 100644 --- a/apps/login/app/(login)/password/page.tsx +++ b/apps/login/app/(login)/password/page.tsx @@ -9,11 +9,15 @@ export default async function Page({ }: { searchParams: Record; }) { - const { loginName, promptPasswordless, authRequestId, alt } = searchParams; - const sessionFactors = await loadSession(loginName); + const { loginName, organization, promptPasswordless, authRequestId, alt } = + searchParams; + const sessionFactors = await loadSession(loginName, organization); - async function loadSession(loginName?: string) { - const recent = await getMostRecentCookieWithLoginname(loginName); + async function loadSession(loginName?: string, organization?: string) { + const recent = await getMostRecentCookieWithLoginname( + loginName, + organization + ); return getSession(server, recent.id, recent.token).then((response) => { if (response?.session) { diff --git a/apps/login/app/api/loginname/route.ts b/apps/login/app/api/loginname/route.ts index cd1823db980..5ae8f8921fc 100644 --- a/apps/login/app/api/loginname/route.ts +++ b/apps/login/app/api/loginname/route.ts @@ -1,5 +1,9 @@ import { listAuthenticationMethodTypes, listUsers } from "#/lib/zitadel"; -import { createSessionAndUpdateCookie } from "#/utils/session"; +import { + createSessionAndUpdateCookie, + createSessionForUserIdAndUpdateCookie, +} from "#/utils/session"; +import { U } from "@zitadel/server/dist/index-79b5dba4"; import { NextRequest, NextResponse } from "next/server"; export async function POST(request: NextRequest) { @@ -7,36 +11,56 @@ export async function POST(request: NextRequest) { if (body) { const { loginName, authRequestId, organization } = body; // TODO - search for users with org - // return listUsers(loginName).then((users) => { - // if (users.details && users.details.totalResult == 1) { - // } - return createSessionAndUpdateCookie( + console.log( + "loginName", loginName, - undefined, - undefined, - authRequestId - ) - .then((session) => { - if (session.factors?.user?.id) { - return listAuthenticationMethodTypes(session.factors?.user?.id) - .then((methods) => { - return NextResponse.json({ - authMethodTypes: methods.authMethodTypes, - sessionId: session.id, - factors: session.factors, - }); - }) - .catch((error) => { - return NextResponse.json(error, { status: 500 }); - }); - } else { - throw { details: "No user id found in session" }; - } - }) - .catch((error) => { - return NextResponse.json(error, { status: 500 }); - }); - // }); + "authRequestId", + authRequestId, + "organization", + organization + ); + return listUsers(loginName, organization).then((users) => { + console.log("users", users); + if ( + users.details && + users.details.totalResult == 1 && + users.result[0].userId + ) { + const userId = users.result[0].userId; + return createSessionForUserIdAndUpdateCookie( + userId, + undefined, + undefined, + authRequestId + ) + .then((session) => { + console.log(session); + if (session.factors?.user?.id) { + return listAuthenticationMethodTypes(session.factors?.user?.id) + .then((methods) => { + return NextResponse.json({ + authMethodTypes: methods.authMethodTypes, + sessionId: session.id, + factors: session.factors, + }); + }) + .catch((error) => { + return NextResponse.json(error, { status: 500 }); + }); + } else { + throw { details: "No user id found in session" }; + } + }) + .catch((error) => { + return NextResponse.json(error, { status: 500 }); + }); + } else { + return NextResponse.json( + { message: "Could not find user" }, + { status: 404 } + ); + } + }); } else { return NextResponse.error(); } diff --git a/apps/login/lib/zitadel.ts b/apps/login/lib/zitadel.ts index ed7412ba4e8..3b62cde750c 100644 --- a/apps/login/lib/zitadel.ts +++ b/apps/login/lib/zitadel.ts @@ -135,6 +135,38 @@ export async function createSessionForLoginname( ); } +export async function createSessionForUserId( + server: ZitadelServer, + userId: string, + password: string | undefined, + challenges: RequestChallenges | undefined +): Promise { + const sessionService = session.getSession(server); + return password + ? sessionService.createSession( + { + checks: { user: { userId }, password: { password } }, + challenges, + // lifetime: { + // seconds: 300, + // nanos: 0, + // }, + }, + {} + ) + : sessionService.createSession( + { + checks: { user: { userId } }, + challenges, + // lifetime: { + // seconds: 300, + // nanos: 0, + // }, + }, + {} + ); +} + export async function createSessionForUserIdAndIdpIntent( server: ZitadelServer, userId: string, @@ -247,10 +279,13 @@ export async function addHumanUser( }); } -export async function listUsers(userName: string): Promise { - // TODO limit for organization +export async function listUsers( + userName: string, + organizationId: string +): Promise { const userService = user.getUser(server); + console.log("listUsers", userName, organizationId); return userService.listUsers( { queries: [ @@ -259,6 +294,11 @@ export async function listUsers(userName: string): Promise { userName, method: TextQueryMethod.TEXT_QUERY_METHOD_EQUALS, }, + organizationIdQuery: organizationId + ? { + organizationId, + } + : undefined, }, ], }, diff --git a/apps/login/ui/UsernameForm.tsx b/apps/login/ui/UsernameForm.tsx index f555ae5a6fd..ed9de338649 100644 --- a/apps/login/ui/UsernameForm.tsx +++ b/apps/login/ui/UsernameForm.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import { Button, ButtonVariants } from "./Button"; import { TextInput } from "./Input"; -import { SubmitHandler, useForm } from "react-hook-form"; +import { useForm } from "react-hook-form"; import { useRouter } from "next/navigation"; import { Spinner } from "./Spinner"; import { LoginSettings } from "@zitadel/server"; @@ -51,12 +51,16 @@ export default function UsernameForm({ body.organization = organization; } + if (authRequestId) { + body.authRequestId = authRequestId; + } + const res = await fetch("/api/loginname", { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify(authRequestId ? { ...body, authRequestId } : body), + body: JSON.stringify(body), }); setLoading(false); @@ -78,7 +82,13 @@ export default function UsernameForm({ const method = response.authMethodTypes[0]; switch (method) { case 1: // user has only password as auth method - const paramsPassword: any = { loginName: values.loginName }; + const paramsPassword: any = { + loginName: response.factors.user.loginName, + }; + + if (organization) { + paramsPassword.organization = organization; + } if (loginSettings?.passkeysType === 1) { paramsPassword.promptPasswordless = `true`; // PasskeysType.PASSKEYS_TYPE_ALLOWED, diff --git a/apps/login/utils/cookies.ts b/apps/login/utils/cookies.ts index ec9dc994651..7fd07d0de43 100644 --- a/apps/login/utils/cookies.ts +++ b/apps/login/utils/cookies.ts @@ -6,6 +6,7 @@ export type SessionCookie = { id: string; token: string; loginName: string; + organization: string; creationDate: string; expirationDate: string; changeDate: string; @@ -226,17 +227,24 @@ export async function getAllSessions( * @returns most recent session */ export async function getMostRecentCookieWithLoginname( - loginName?: string + loginName?: string, + organization?: string ): Promise { const cookiesList = cookies(); const stringifiedCookie = cookiesList.get("sessions"); if (stringifiedCookie?.value) { const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); - const filtered = sessions.filter((cookie) => { + 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) => { diff --git a/apps/login/utils/session.ts b/apps/login/utils/session.ts index 575cbdb823b..90898e523c2 100644 --- a/apps/login/utils/session.ts +++ b/apps/login/utils/session.ts @@ -2,6 +2,7 @@ import { createSessionForLoginname, + createSessionForUserId, createSessionForUserIdAndIdpIntent, getSession, server, @@ -40,7 +41,54 @@ export async function createSessionAndUpdateCookie( creationDate: response.session.creationDate?.toString() ?? "", expirationDate: (response.session.expirationDate ?? "")?.toString(), changeDate: response.session.changeDate?.toString() ?? "", - loginName: response.session?.factors?.user?.loginName ?? "", + loginName: response.session.factors.user.loginName ?? "", + organization: response.session.factors.user.organizationId ?? "", + }; + + if (authRequestId) { + sessionCookie.authRequestId = authRequestId; + } + + return addSessionToCookie(sessionCookie).then(() => { + return response.session as Session; + }); + } else { + throw "could not get session or session does not have loginName"; + } + }); + } else { + throw "Could not create session"; + } +} + +export async function createSessionForUserIdAndUpdateCookie( + userId: string, + password: string | undefined, + challenges: RequestChallenges | undefined, + authRequestId: string | undefined +): Promise { + const createdSession = await createSessionForUserId( + server, + userId, + password, + challenges + ); + + if (createdSession) { + return getSession( + server, + createdSession.sessionId, + createdSession.sessionToken + ).then((response) => { + if (response?.session && response.session?.factors?.user?.loginName) { + const sessionCookie: SessionCookie = { + id: createdSession.sessionId, + token: createdSession.sessionToken, + creationDate: response.session.creationDate?.toString() ?? "", + expirationDate: (response.session.expirationDate ?? "")?.toString(), + changeDate: response.session.changeDate?.toString() ?? "", + loginName: response.session.factors.user.loginName ?? "", + organization: response.session.factors.user.organizationId ?? "", }; if (authRequestId) { @@ -86,7 +134,8 @@ export async function createSessionForIdpAndUpdateCookie( creationDate: response.session.creationDate?.toString() ?? "", expirationDate: (response.session.expirationDate ?? "")?.toString(), changeDate: response.session.changeDate?.toString() ?? "", - loginName: response.session?.factors?.user?.loginName ?? "", + loginName: response.session.factors.user.loginName ?? "", + organization: response.session.factors.user.organizationId ?? "", }; if (authRequestId) { @@ -132,6 +181,7 @@ export async function setSessionAndUpdateCookie( expirationDate: recentCookie.expirationDate, changeDate: updatedSession.details?.changeDate?.toString() ?? "", loginName: recentCookie.loginName, + organization: recentCookie.organization, }; if (authRequestId) { @@ -154,6 +204,7 @@ export async function setSessionAndUpdateCookie( expirationDate: sessionCookie.expirationDate, changeDate: session.changeDate?.toString() ?? "", loginName: session.factors?.user?.loginName ?? "", + organization: session.factors?.user?.organizationId ?? "", }; if (sessionCookie.authRequestId) {