diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index f5741bb39c5..de38d4bcb63 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -22,16 +22,18 @@ import { } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; export const dynamic = "force-dynamic"; export const revalidate = false; export const fetchCache = "default-no-store"; -async function loadSessions(ids: string[]): Promise { - const response = await listSessions( - ids.filter((id: string | undefined) => !!id), - ); +async function loadSessions(host: string, ids: string[]): Promise { + const response = await listSessions({ + host, + ids: ids.filter((id: string | undefined) => !!id), + }); return response?.sessions ?? []; } @@ -44,7 +46,10 @@ const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/; * mfa is required, session is not valid anymore (e.g. session expired, user logged out, etc.) * to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId); **/ -async function isSessionValid(session: Session): Promise { +async function isSessionValid( + host: string, + session: Session, +): Promise { // session can't be checked without user if (!session.factors?.user) { console.warn("Session has no user"); @@ -53,9 +58,10 @@ async function isSessionValid(session: Session): Promise { let mfaValid = true; - const authMethodTypes = await listAuthenticationMethodTypes( - session.factors.user.id, - ); + const authMethodTypes = await listAuthenticationMethodTypes({ + host, + userId: session.factors.user.id, + }); const authMethods = authMethodTypes.authMethodTypes; if (authMethods && authMethods.includes(AuthenticationMethodType.TOTP)) { @@ -101,9 +107,10 @@ async function isSessionValid(session: Session): Promise { } } else { // only check settings if no auth methods are available, as this would require a setup - const loginSettings = await getLoginSettings( - session.factors?.user?.organizationId, - ); + const loginSettings = await getLoginSettings({ + host, + organization: session.factors?.user?.organizationId, + }); if (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) { const otpEmail = session.factors.otpEmail?.verifiedAt; const otpSms = session.factors.otpSms?.verifiedAt; @@ -144,6 +151,7 @@ async function isSessionValid(session: Session): Promise { } async function findValidSession( + host: string, sessions: Session[], authRequest: AuthRequest, ): Promise { @@ -170,7 +178,7 @@ async function findValidSession( // return the first valid session according to settings for (const session of sessionsWithHint) { - if (await isSessionValid(session)) { + if (await isSessionValid(host, session)) { return session; } } @@ -183,6 +191,12 @@ export async function GET(request: NextRequest) { const authRequestId = searchParams.get("authRequest"); const sessionId = searchParams.get("sessionId"); + const host = (await headers()).get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + // TODO: find a better way to handle _rsc (react server components) requests and block them to avoid conflicts when creating oidc callback const _rsc = searchParams.get("_rsc"); if (_rsc) { @@ -193,7 +207,7 @@ export async function GET(request: NextRequest) { const ids = sessionCookies.map((s) => s.id); let sessions: Session[] = []; if (ids && ids.length) { - sessions = await loadSessions(ids); + sessions = await loadSessions(host, ids); } if (authRequestId && sessionId) { @@ -206,7 +220,7 @@ export async function GET(request: NextRequest) { if (selectedSession && selectedSession.id) { console.log(`Found session ${selectedSession.id}`); - const isValid = await isSessionValid(selectedSession); + const isValid = await isSessionValid(host, selectedSession); console.log("Session is valid:", isValid); @@ -239,15 +253,16 @@ export async function GET(request: NextRequest) { // works not with _rsc request try { - const { callbackUrl } = await createCallback( - create(CreateCallbackRequestSchema, { + const { callbackUrl } = await createCallback({ + host, + req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { case: "session", value: create(SessionSchema, session), }, }), - ); + }); if (callbackUrl) { return NextResponse.redirect(callbackUrl); } else { @@ -265,9 +280,10 @@ export async function GET(request: NextRequest) { "code" in error && error?.code === 9 ) { - const loginSettings = await getLoginSettings( - selectedSession.factors?.user?.organizationId, - ); + const loginSettings = await getLoginSettings({ + host, + organization: selectedSession.factors?.user?.organizationId, + }); if (loginSettings?.defaultRedirectUri) { return NextResponse.redirect(loginSettings.defaultRedirectUri); @@ -297,7 +313,7 @@ export async function GET(request: NextRequest) { } if (authRequestId) { - const { authRequest } = await getAuthRequest({ authRequestId }); + const { authRequest } = await getAuthRequest({ host, authRequestId }); let organization = ""; let suffix = ""; @@ -324,7 +340,7 @@ export async function GET(request: NextRequest) { const matched = ORG_DOMAIN_SCOPE_REGEX.exec(orgDomainScope); const orgDomain = matched?.[1] ?? ""; if (orgDomain) { - const orgs = await getOrgsByDomain(orgDomain); + const orgs = await getOrgsByDomain({ host, domain: orgDomain }); if (orgs.result && orgs.result.length === 1) { organization = orgs.result[0].id ?? ""; suffix = orgDomain; @@ -337,9 +353,10 @@ export async function GET(request: NextRequest) { const matched = IDP_SCOPE_REGEX.exec(idpScope); idpId = matched?.[1] ?? ""; - const identityProviders = await getActiveIdentityProviders( - organization ? organization : undefined, - ).then((resp) => { + const identityProviders = await getActiveIdentityProviders({ + host, + orgId: organization ? organization : undefined, + }).then((resp) => { return resp.identityProviders; }); @@ -362,6 +379,7 @@ export async function GET(request: NextRequest) { } return startIdentityProviderFlow({ + host, idpId, urls: { successUrl: @@ -460,7 +478,11 @@ export async function GET(request: NextRequest) { * This means that the user should not be prompted to enter their password again. * Instead, the server attempts to silently authenticate the user using an existing session or other authentication mechanisms that do not require user interaction **/ - const selectedSession = await findValidSession(sessions, authRequest); + const selectedSession = await findValidSession( + host, + sessions, + authRequest, + ); if (!selectedSession || !selectedSession.id) { return NextResponse.json( @@ -485,19 +507,24 @@ export async function GET(request: NextRequest) { sessionToken: cookie.token, }; - const { callbackUrl } = await createCallback( - create(CreateCallbackRequestSchema, { + const { callbackUrl } = await createCallback({ + host, + req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { case: "session", value: create(SessionSchema, session), }, }), - ); + }); return NextResponse.redirect(callbackUrl); } else { // check for loginHint, userId hint and valid sessions - let selectedSession = await findValidSession(sessions, authRequest); + let selectedSession = await findValidSession( + host, + sessions, + authRequest, + ); if (!selectedSession || !selectedSession.id) { return gotoAccounts(); @@ -517,15 +544,16 @@ export async function GET(request: NextRequest) { }; try { - const { callbackUrl } = await createCallback( - create(CreateCallbackRequestSchema, { + const { callbackUrl } = await createCallback({ + host, + req: create(CreateCallbackRequestSchema, { authRequestId, callbackKind: { case: "session", value: create(SessionSchema, session), }, }), - ); + }); if (callbackUrl) { return NextResponse.redirect(callbackUrl); } else {