fix skip passwordless prompt, register with authrequest, passkey

This commit is contained in:
peintnermax
2024-03-25 15:18:51 +01:00
parent a9d0752b5f
commit 7dd709e3e7
13 changed files with 131 additions and 49 deletions

View File

@@ -9,22 +9,25 @@ export default async function Page({
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
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 && (
<RegisterPasskey sessionId={sessionFactors.id} isPrompt={!!prompt} />
<RegisterPasskey
sessionId={sessionFactors.id}
isPrompt={!!promptPasswordless}
/>
)}
</div>
);

View File

@@ -13,12 +13,15 @@ export default async function Page({
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
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}
/>
)}
</div>

View File

@@ -11,13 +11,15 @@ export default async function Page({
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
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}
></SetPasswordForm>
)}
</div>
@@ -42,6 +46,8 @@ export default async function Page({
{legal && passwordComplexitySettings && (
<RegisterFormWithoutPassword
legal={legal}
organization={organization}
authRequestId={authRequestId}
></RegisterFormWithoutPassword>
)}
</div>

View File

@@ -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;

View File

@@ -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<SessionCookie> = loginName
? getSessionCookieByLoginName(loginName).catch((error) => {
? getSessionCookieByLoginName(loginName, organization).catch((error) => {
return Promise.reject(error);
})
: getMostRecentSessionCookie().catch((error) => {

View File

@@ -94,12 +94,18 @@ export async function getLegalAndSupportSettings(
}
export async function getPasswordComplexitySettings(
server: ZitadelServer
server: ZitadelServer,
organization?: string
): Promise<PasswordComplexitySettings | undefined> {
const settingsService = settings.getSettings(server);
return settingsService
.getPasswordComplexitySettings({}, {})
.getPasswordComplexitySettings(
organization
? { ctx: { orgId: organization } }
: { ctx: { instance: true } },
{}
)
.then((resp: GetPasswordComplexitySettingsResponse) => resp.settings);
}

View File

@@ -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,
}),
});

View File

@@ -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<string>("");
const [loading, setLoading] = useState<boolean>(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,
}),

View File

@@ -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,26 +72,30 @@ 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({
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({
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(
const params = new URLSearchParams(
authRequestId
? {
loginName: resp.factors.user.loginName,
@@ -97,8 +104,13 @@ export default function PasswordForm({
: {
loginName: resp.factors.user.loginName,
}
)
);
if (organization) {
params.append("organization", organization);
}
return router.push(`/signedin?` + params);
}
}
});

View File

@@ -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<Inputs>({
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,
}),
});

View File

@@ -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<Inputs>({
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
}),
});

View File

@@ -148,7 +148,8 @@ export async function getSessionCookieById(id: string): Promise<SessionCookie> {
}
export async function getSessionCookieByLoginName(
loginName: string
loginName: string,
organization?: string
): Promise<SessionCookie> {
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 {

View File

@@ -19,7 +19,8 @@ export async function createSessionAndUpdateCookie(
loginName: string,
password: string | undefined,
challenges: RequestChallenges | undefined,
authRequestId: string | undefined
organization?: string,
authRequestId?: string
): Promise<Session> {
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<Session> {
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;
});