mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 14:42:17 +00:00
fix skip passwordless prompt, register with authrequest, passkey
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user