From 7dd709e3e728c6aff1b351d41b31bd0d6da96514 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Mon, 25 Mar 2024 15:18:51 +0100 Subject: [PATCH] fix skip passwordless prompt, register with authrequest, passkey --- apps/login/app/(login)/passkey/add/page.tsx | 16 +++-- apps/login/app/(login)/passkey/login/page.tsx | 12 ++-- apps/login/app/(login)/register/page.tsx | 10 ++- apps/login/app/api/passkeys/route.ts | 1 + apps/login/app/api/session/route.ts | 17 +++-- apps/login/lib/zitadel.ts | 10 ++- apps/login/ui/IdpSignin.tsx | 2 + apps/login/ui/LoginPasskey.tsx | 4 ++ apps/login/ui/PasswordForm.tsx | 64 +++++++++++-------- apps/login/ui/RegisterFormWithoutPassword.tsx | 11 +++- apps/login/ui/SetPasswordForm.tsx | 10 ++- apps/login/utils/cookies.ts | 9 ++- apps/login/utils/session.ts | 14 +++- 13 files changed, 131 insertions(+), 49 deletions(-) diff --git a/apps/login/app/(login)/passkey/add/page.tsx b/apps/login/app/(login)/passkey/add/page.tsx index 09a18e2ba07..08a4c36dcd5 100644 --- a/apps/login/app/(login)/passkey/add/page.tsx +++ b/apps/login/app/(login)/passkey/add/page.tsx @@ -9,22 +9,25 @@ export default async function Page({ }: { searchParams: Record; }) { - const { loginName, prompt, organization } = searchParams; + const { loginName, promptPasswordless, organization } = searchParams; const sessionFactors = await loadSession(loginName); async function loadSession(loginName?: string) { - const recent = await getMostRecentCookieWithLoginname(loginName); + const recent = await getMostRecentCookieWithLoginname( + loginName, + organization + ); return getSession(server, recent.id, recent.token).then((response) => { if (response?.session) { return response.session; } }); } - const title = !!prompt + const title = !!promptPasswordless ? "Authenticate with a passkey" : "Use your passkey to confirm it's really you"; - const description = !!prompt + const description = !!promptPasswordless ? "When set up, you will be able to authenticate without a password." : "Your device will ask for your fingerprint, face, or screen lock"; @@ -65,7 +68,10 @@ export default async function Page({ )} {sessionFactors?.id && ( - + )} ); diff --git a/apps/login/app/(login)/passkey/login/page.tsx b/apps/login/app/(login)/passkey/login/page.tsx index e523ff30221..c16435aa9ad 100644 --- a/apps/login/app/(login)/passkey/login/page.tsx +++ b/apps/login/app/(login)/passkey/login/page.tsx @@ -13,12 +13,15 @@ export default async function Page({ }: { searchParams: Record; }) { - const { loginName, altPassword, authRequestId } = searchParams; + const { loginName, altPassword, authRequestId, organization } = searchParams; - const sessionFactors = await loadSession(loginName); + 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) { return response.session; @@ -50,6 +53,7 @@ export default async function Page({ loginName={loginName} authRequestId={authRequestId} altPassword={altPassword === "true"} + organization={organization} /> )} diff --git a/apps/login/app/(login)/register/page.tsx b/apps/login/app/(login)/register/page.tsx index 9cd32eb07da..646ed2296c4 100644 --- a/apps/login/app/(login)/register/page.tsx +++ b/apps/login/app/(login)/register/page.tsx @@ -11,13 +11,15 @@ export default async function Page({ }: { searchParams: Record; }) { - const { firstname, lastname, email } = searchParams; + const { firstname, lastname, email, organization, authRequestId } = + searchParams; const setPassword = !!(firstname && lastname && email); const legal = await getLegalAndSupportSettings(server); const passwordComplexitySettings = await getPasswordComplexitySettings( - server + server, + organization ); return setPassword ? ( @@ -31,6 +33,8 @@ export default async function Page({ email={email} firstname={firstname} lastname={lastname} + organization={organization} + authRequestId={authRequestId} > )} @@ -42,6 +46,8 @@ export default async function Page({ {legal && passwordComplexitySettings && ( )} diff --git a/apps/login/app/api/passkeys/route.ts b/apps/login/app/api/passkeys/route.ts index 3cbd4b71bcd..55990262132 100644 --- a/apps/login/app/api/passkeys/route.ts +++ b/apps/login/app/api/passkeys/route.ts @@ -25,6 +25,7 @@ export async function POST(request: NextRequest) { const userId = session?.session?.factors?.user?.id; if (userId) { + // TODO: add org context return createPasskeyRegistrationLink(userId) .then((resp) => { const code = resp.code; diff --git a/apps/login/app/api/session/route.ts b/apps/login/app/api/session/route.ts index 0337d05b86e..6a0a8a2b1a1 100644 --- a/apps/login/app/api/session/route.ts +++ b/apps/login/app/api/session/route.ts @@ -17,12 +17,20 @@ import { NextRequest, NextResponse } from "next/server"; export async function POST(request: NextRequest) { const body = await request.json(); if (body) { - const { userId, idpIntent, loginName, password, authRequestId } = body; + const { + userId, + idpIntent, + loginName, + password, + organization, + authRequestId, + } = body; if (userId && idpIntent) { return createSessionForIdpAndUpdateCookie( userId, idpIntent, + organization, authRequestId ).then((session) => { return NextResponse.json(session); @@ -32,7 +40,8 @@ export async function POST(request: NextRequest) { loginName, password, undefined, - undefined + organization, + authRequestId ).then((session) => { return NextResponse.json(session); }); @@ -54,11 +63,11 @@ export async function PUT(request: NextRequest) { const body = await request.json(); if (body) { - const { loginName, password, webAuthN, authRequestId } = body; + const { loginName, organization, password, webAuthN, authRequestId } = body; const challenges: RequestChallenges = body.challenges; const recentPromise: Promise = loginName - ? getSessionCookieByLoginName(loginName).catch((error) => { + ? getSessionCookieByLoginName(loginName, organization).catch((error) => { return Promise.reject(error); }) : getMostRecentSessionCookie().catch((error) => { diff --git a/apps/login/lib/zitadel.ts b/apps/login/lib/zitadel.ts index 3b62cde750c..ffd1b968c3d 100644 --- a/apps/login/lib/zitadel.ts +++ b/apps/login/lib/zitadel.ts @@ -94,12 +94,18 @@ export async function getLegalAndSupportSettings( } export async function getPasswordComplexitySettings( - server: ZitadelServer + server: ZitadelServer, + organization?: string ): Promise { const settingsService = settings.getSettings(server); return settingsService - .getPasswordComplexitySettings({}, {}) + .getPasswordComplexitySettings( + organization + ? { ctx: { orgId: organization } } + : { ctx: { instance: true } }, + {} + ) .then((resp: GetPasswordComplexitySettingsResponse) => resp.settings); } diff --git a/apps/login/ui/IdpSignin.tsx b/apps/login/ui/IdpSignin.tsx index ff3a4cf6033..bd459a04164 100644 --- a/apps/login/ui/IdpSignin.tsx +++ b/apps/login/ui/IdpSignin.tsx @@ -7,6 +7,7 @@ import { useRouter } from "next/navigation"; type Props = { userId: string; + organization: string; idpIntent: { idpIntentId: string; idpIntentToken: string; @@ -31,6 +32,7 @@ export default function IdpSignin(props: Props) { userId: props.userId, idpIntent: props.idpIntent, authRequestId: props.authRequestId, + organization: props.organization, }), }); diff --git a/apps/login/ui/LoginPasskey.tsx b/apps/login/ui/LoginPasskey.tsx index 88b3f924bb0..e7c5c05dd3b 100644 --- a/apps/login/ui/LoginPasskey.tsx +++ b/apps/login/ui/LoginPasskey.tsx @@ -11,12 +11,14 @@ type Props = { loginName: string; authRequestId?: string; altPassword: boolean; + organization?: string; }; export default function LoginPasskey({ loginName, authRequestId, altPassword, + organization, }: Props) { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -64,6 +66,7 @@ export default function LoginPasskey({ }, body: JSON.stringify({ loginName, + organization, challenges: { webAuthN: { domain: "", @@ -91,6 +94,7 @@ export default function LoginPasskey({ }, body: JSON.stringify({ loginName, + organization, webAuthN: { credentialAssertionData: data }, authRequestId, }), diff --git a/apps/login/ui/PasswordForm.tsx b/apps/login/ui/PasswordForm.tsx index 49684fa6180..ebdb9ed7659 100644 --- a/apps/login/ui/PasswordForm.tsx +++ b/apps/login/ui/PasswordForm.tsx @@ -14,6 +14,7 @@ type Inputs = { type Props = { loginName?: string; + organization?: string; authRequestId?: string; isAlternative?: boolean; // whether password was requested as alternative auth method promptPasswordless?: boolean; @@ -21,6 +22,7 @@ type Props = { export default function PasswordForm({ loginName, + organization, authRequestId, promptPasswordless, isAlternative, @@ -46,6 +48,7 @@ export default function PasswordForm({ }, body: JSON.stringify({ loginName, + organization, password: values.password, authRequestId, }), @@ -69,36 +72,45 @@ export default function PasswordForm({ promptPasswordless && // if explicitly prompted due policy !isAlternative // escaped if password was used as an alternative method ) { - return router.push( - `/passkey/add?` + - new URLSearchParams({ - loginName: resp.factors.user.loginName, - promptPasswordless: "true", - }) - ); + const params = new URLSearchParams({ + loginName: resp.factors.user.loginName, + promptPasswordless: "true", + }); + + if (organization) { + params.append("organization", organization); + } + + return router.push(`/passkey/add?` + params); } else { if (authRequestId && resp && resp.sessionId) { - return router.push( - `/login?` + - new URLSearchParams({ - sessionId: resp.sessionId, - authRequest: authRequestId, - }) - ); + const params = new URLSearchParams({ + sessionId: resp.sessionId, + authRequest: authRequestId, + }); + + if (organization) { + params.append("organization", organization); + } + + return router.push(`/login?` + params); } else { - return router.push( - `/signedin?` + - new URLSearchParams( - authRequestId - ? { - loginName: resp.factors.user.loginName, - authRequestId, - } - : { - loginName: resp.factors.user.loginName, - } - ) + const params = new URLSearchParams( + authRequestId + ? { + loginName: resp.factors.user.loginName, + authRequestId, + } + : { + loginName: resp.factors.user.loginName, + } ); + + if (organization) { + params.append("organization", organization); + } + + return router.push(`/signedin?` + params); } } }); diff --git a/apps/login/ui/RegisterFormWithoutPassword.tsx b/apps/login/ui/RegisterFormWithoutPassword.tsx index 49a89df5cf1..46d1589bfde 100644 --- a/apps/login/ui/RegisterFormWithoutPassword.tsx +++ b/apps/login/ui/RegisterFormWithoutPassword.tsx @@ -23,9 +23,15 @@ type Inputs = type Props = { legal: LegalAndSupportSettings; + organization?: string; + authRequestId?: string; }; -export default function RegisterFormWithoutPassword({ legal }: Props) { +export default function RegisterFormWithoutPassword({ + legal, + organization, + authRequestId, +}: Props) { const { register, handleSubmit, formState } = useForm({ mode: "onBlur", }); @@ -66,7 +72,8 @@ export default function RegisterFormWithoutPassword({ legal }: Props) { }, body: JSON.stringify({ loginName: loginName, - // authRequestId, register does not need an oidc callback at the end + organization: organization, + authRequestId: authRequestId, }), }); diff --git a/apps/login/ui/SetPasswordForm.tsx b/apps/login/ui/SetPasswordForm.tsx index 6b065a376ff..c8537c140aa 100644 --- a/apps/login/ui/SetPasswordForm.tsx +++ b/apps/login/ui/SetPasswordForm.tsx @@ -15,6 +15,7 @@ import { import { useRouter } from "next/navigation"; import { Spinner } from "./Spinner"; import Alert from "./Alert"; +import { AuthRequest } from "@zitadel/server"; type Inputs = | { @@ -28,6 +29,8 @@ type Props = { email: string; firstname: string; lastname: string; + organization?: string; + authRequestId?: string; }; export default function SetPasswordForm({ @@ -35,6 +38,8 @@ export default function SetPasswordForm({ email, firstname, lastname, + organization, + authRequestId, }: Props) { const { register, handleSubmit, watch, formState } = useForm({ mode: "onBlur", @@ -56,6 +61,8 @@ export default function SetPasswordForm({ email: email, firstName: firstname, lastName: lastname, + organization: organization, + authRequestId: authRequestId, password: values.password, }), }); @@ -79,7 +86,8 @@ export default function SetPasswordForm({ body: JSON.stringify({ loginName: loginName, password: password, - // authRequestId, register does not need an oidc callback + organization: organization, + authRequestId, //, register does not need an oidc callback }), }); diff --git a/apps/login/utils/cookies.ts b/apps/login/utils/cookies.ts index 7fd07d0de43..7512afab3e0 100644 --- a/apps/login/utils/cookies.ts +++ b/apps/login/utils/cookies.ts @@ -148,7 +148,8 @@ export async function getSessionCookieById(id: string): Promise { } export async function getSessionCookieByLoginName( - loginName: string + loginName: string, + organization?: string ): Promise { const cookiesList = cookies(); const stringifiedCookie = cookiesList.get("sessions"); @@ -156,7 +157,11 @@ export async function getSessionCookieByLoginName( if (stringifiedCookie?.value) { const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); - const found = sessions.find((s) => s.loginName === loginName); + const found = sessions.find((s) => + s.loginName === loginName && organization + ? s.organization === organization + : true + ); if (found) { return found; } else { diff --git a/apps/login/utils/session.ts b/apps/login/utils/session.ts index 90898e523c2..b669c18454a 100644 --- a/apps/login/utils/session.ts +++ b/apps/login/utils/session.ts @@ -19,7 +19,8 @@ export async function createSessionAndUpdateCookie( loginName: string, password: string | undefined, challenges: RequestChallenges | undefined, - authRequestId: string | undefined + organization?: string, + authRequestId?: string ): Promise { const createdSession = await createSessionForLoginname( server, @@ -49,6 +50,10 @@ export async function createSessionAndUpdateCookie( sessionCookie.authRequestId = authRequestId; } + if (organization) { + sessionCookie.organization = organization; + } + return addSessionToCookie(sessionCookie).then(() => { return response.session as Session; }); @@ -95,6 +100,8 @@ export async function createSessionForUserIdAndUpdateCookie( sessionCookie.authRequestId = authRequestId; } + if () + return addSessionToCookie(sessionCookie).then(() => { return response.session as Session; }); @@ -113,6 +120,7 @@ export async function createSessionForIdpAndUpdateCookie( idpIntentId?: string | undefined; idpIntentToken?: string | undefined; }, + organization: string | undefined, authRequestId: string | undefined ): Promise { const createdSession = await createSessionForUserIdAndIdpIntent( @@ -142,6 +150,10 @@ export async function createSessionForIdpAndUpdateCookie( sessionCookie.authRequestId = authRequestId; } + if (organization) { + sessionCookie.organization = organization; + } + return addSessionToCookie(sessionCookie).then(() => { return response.session as Session; });