Merge branch 'qa' into idp-linking

This commit is contained in:
Max Peintner
2024-08-21 16:11:36 +02:00
committed by GitHub
14 changed files with 80 additions and 126 deletions

View File

@@ -18,14 +18,19 @@ export async function POST(request: NextRequest) {
const session = await getSession(sessionCookie.id, sessionCookie.token); const session = await getSession(sessionCookie.id, sessionCookie.token);
const userId = session?.session?.factors?.user?.id; const userId = session?.session?.factors?.user?.id;
console.log("payload", {
if (userId) {
return verifyPasskeyRegistration(
passkeyId, passkeyId,
passkeyName, passkeyName,
publicKeyCredential, publicKeyCredential,
userId, userId,
) });
if (userId) {
return verifyPasskeyRegistration({
passkeyId,
passkeyName,
publicKeyCredential,
userId,
})
.then((resp) => { .then((resp) => {
return NextResponse.json(resp); return NextResponse.json(resp);
}) })

View File

@@ -22,12 +22,15 @@ export async function POST(request: NextRequest) {
const userId = session?.session?.factors?.user?.id; const userId = session?.session?.factors?.user?.id;
if (userId) { if (userId) {
const req: PlainMessage<VerifyU2FRegistrationRequest> = { let req: PlainMessage<VerifyU2FRegistrationRequest> = {
publicKeyCredential, publicKeyCredential,
u2fId, u2fId,
userId, userId,
tokenName: passkeyName, tokenName: passkeyName,
}; };
req = VerifyU2FRegistrationRequest.fromJson(request as any);
return verifyU2FRegistration(req) return verifyU2FRegistration(req)
.then((resp) => { .then((resp) => {
return NextResponse.json(resp); return NextResponse.json(resp);

View File

@@ -12,6 +12,7 @@ import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
import { import {
RetrieveIdentityProviderIntentRequest, RetrieveIdentityProviderIntentRequest,
VerifyPasskeyRegistrationRequest,
VerifyU2FRegistrationRequest, VerifyU2FRegistrationRequest,
} from "@zitadel/proto/zitadel/user/v2/user_service_pb"; } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb";
@@ -482,24 +483,11 @@ export async function getActiveIdentityProviders(orgId?: string) {
* @returns the newly set email * @returns the newly set email
*/ */
export async function verifyPasskeyRegistration( export async function verifyPasskeyRegistration(
passkeyId: string, request: PartialMessage<VerifyPasskeyRegistrationRequest>,
passkeyName: string,
publicKeyCredential:
| {
[key: string]: any;
}
| undefined,
userId: string,
) { ) {
return userService.verifyPasskeyRegistration( // TODO: find a better way to handle this
{ request = VerifyPasskeyRegistrationRequest.fromJson(request as any);
passkeyId, return userService.verifyPasskeyRegistration(request, {});
passkeyName,
publicKeyCredential,
userId,
},
{},
);
} }
/** /**

View File

@@ -18,11 +18,14 @@ export function middleware(request: NextRequest) {
requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID); requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID);
// this is a workaround for the next.js server not forwarding the host header // this is a workaround for the next.js server not forwarding the host header
requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); // requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`);
// requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`); requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`);
// this is a workaround for the next.js server not forwarding the host header // this is a workaround for the next.js server not forwarding the host header
// requestHeaders.set("x-zitadel-instance-host", `${INSTANCE}`); requestHeaders.set(
"x-zitadel-instance-host",
`${INSTANCE}`.replace("https://", ""),
);
const responseHeaders = new Headers(); const responseHeaders = new Headers();
responseHeaders.set("Access-Control-Allow-Origin", "*"); responseHeaders.set("Access-Control-Allow-Origin", "*");

View File

@@ -8,6 +8,7 @@ import Alert from "./Alert";
import { Spinner } from "./Spinner"; import { Spinner } from "./Spinner";
import BackButton from "./BackButton"; import BackButton from "./BackButton";
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
// either loginName or sessionId must be provided // either loginName or sessionId must be provided
type Props = { type Props = {
@@ -78,7 +79,7 @@ export default function LoginPasskey({
loginName, loginName,
sessionId, sessionId,
organization, organization,
challenges: { challenges: RequestChallenges.fromJson({
webAuthN: { webAuthN: {
domain: "", domain: "",
// USER_VERIFICATION_REQUIREMENT_UNSPECIFIED = 0; // USER_VERIFICATION_REQUIREMENT_UNSPECIFIED = 0;
@@ -87,7 +88,7 @@ export default function LoginPasskey({
// USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 3; - mfa // USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 3; - mfa
userVerificationRequirement: userVerificationRequirement, userVerificationRequirement: userVerificationRequirement,
}, },
}, }),
authRequestId, authRequestId,
}), }),
}); });

View File

@@ -92,7 +92,7 @@ export default function RegisterPasskey({
return submitRegister().then((resp: RegisterPasskeyResponse) => { return submitRegister().then((resp: RegisterPasskeyResponse) => {
const passkeyId = resp.passkeyId; const passkeyId = resp.passkeyId;
const options: CredentialCreationOptions = const options: CredentialCreationOptions =
(resp.publicKeyCredentialCreationOptions?.toJson() as CredentialCreationOptions) ?? (resp.publicKeyCredentialCreationOptions as CredentialCreationOptions) ??
{}; {};
if (options?.publicKey) { if (options?.publicKey) {
@@ -143,6 +143,7 @@ export default function RegisterPasskey({
), ),
}, },
}; };
return submitVerify(passkeyId, "", data, sessionId).then(() => { return submitVerify(passkeyId, "", data, sessionId).then(() => {
const params = new URLSearchParams(); const params = new URLSearchParams();
@@ -194,10 +195,11 @@ export default function RegisterPasskey({
type="button" type="button"
variant={ButtonVariants.Secondary} variant={ButtonVariants.Secondary}
onClick={() => { onClick={() => {
const params = new URLSearchParams();
if (authRequestId) { if (authRequestId) {
params.set("authRequest", authRequestId); const params = new URLSearchParams({
} authRequest: authRequestId,
});
if (sessionId) { if (sessionId) {
params.set("sessionId", sessionId); params.set("sessionId", sessionId);
} }
@@ -207,6 +209,18 @@ export default function RegisterPasskey({
} }
router.push("/login?" + params); router.push("/login?" + params);
} else {
const params = new URLSearchParams();
if (sessionId) {
params.append("sessionId", sessionId);
}
if (organization) {
params.append("organization", organization);
}
router.push("/signedin?" + params);
}
}} }}
> >
skip skip

View File

@@ -90,7 +90,7 @@ export default function RegisterU2F({
return submitRegister().then((resp: RegisterU2FResponse) => { return submitRegister().then((resp: RegisterU2FResponse) => {
const u2fId = resp.u2fId; const u2fId = resp.u2fId;
const options: CredentialCreationOptions = const options: CredentialCreationOptions =
(resp.publicKeyCredentialCreationOptions?.toJson() as CredentialCreationOptions) ?? (resp.publicKeyCredentialCreationOptions as CredentialCreationOptions) ??
{}; {};
if (options.publicKey) { if (options.publicKey) {

View File

@@ -121,11 +121,12 @@ export default function SessionItem({
<XCircleIcon <XCircleIcon
className="hidden group-hover:block h-5 w-5 transition-all opacity-50 hover:opacity-100" className="hidden group-hover:block h-5 w-5 transition-all opacity-50 hover:opacity-100"
onClick={() => onClick={(event) => {
event.preventDefault();
clearSession(session.id).then(() => { clearSession(session.id).then(() => {
reload(); reload();
}) });
} }}
/> />
</div> </div>
</Link> </Link>

View File

@@ -38,7 +38,6 @@ export async function createSessionAndUpdateCookie(
? { ? {
user: { search: { case: "loginName", value: loginName } }, user: { search: { case: "loginName", value: loginName } },
password: { password }, password: { password },
// totp: { code: totpCode },
} }
: { user: { search: { case: "loginName", value: loginName } } }, : { user: { search: { case: "loginName", value: loginName } } },
challenges, challenges,
@@ -50,7 +49,7 @@ export async function createSessionAndUpdateCookie(
createdSession.sessionToken, createdSession.sessionToken,
).then((response) => { ).then((response) => {
if (response?.session && response.session?.factors?.user?.loginName) { if (response?.session && response.session?.factors?.user?.loginName) {
const sessionCookie: any = { const sessionCookie: CustomCookieData = {
id: createdSession.sessionId, id: createdSession.sessionId,
token: createdSession.sessionToken, token: createdSession.sessionToken,
creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`, creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`,
@@ -103,7 +102,7 @@ export async function createSessionForUserIdAndUpdateCookie(
createdSession.sessionToken, createdSession.sessionToken,
).then((response) => { ).then((response) => {
if (response?.session && response.session?.factors?.user?.loginName) { if (response?.session && response.session?.factors?.user?.loginName) {
const sessionCookie: any = { const sessionCookie: CustomCookieData = {
id: createdSession.sessionId, id: createdSession.sessionId,
token: createdSession.sessionToken, token: createdSession.sessionToken,
creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`, creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`,
@@ -153,7 +152,7 @@ export async function createSessionForIdpAndUpdateCookie(
createdSession.sessionToken, createdSession.sessionToken,
).then((response) => { ).then((response) => {
if (response?.session && response.session?.factors?.user?.loginName) { if (response?.session && response.session?.factors?.user?.loginName) {
const sessionCookie: any = { const sessionCookie: CustomCookieData = {
id: createdSession.sessionId, id: createdSession.sessionId,
token: createdSession.sessionToken, token: createdSession.sessionToken,
creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`, creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`,

View File

@@ -2,39 +2,20 @@
import { ReactNode, forwardRef } from "react"; import { ReactNode, forwardRef } from "react";
import { SignInWithIdentityProviderProps } from "./SignInWith"; import { SignInWithIdentityProviderProps } from "./SignInWith";
import { IdpButtonClasses } from "./classes";
export const SignInWithAzureAD = forwardRef< export const SignInWithAzureAD = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
HTMLButtonElement,
SignInWithIdentityProviderProps
>(
({ children, className = "", name = "", ...props }, ref): ReactNode => ( ({ children, className = "", name = "", ...props }, ref): ReactNode => (
<button <button type="button" ref={ref} className={`${IdpButtonClasses} ${className}`} {...props}>
type="button"
ref={ref}
className={`ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center ztdl-bg-white ztdl-text-black dark:ztdl-bg-transparent dark:ztdl-text-white border ztdl-border-divider-light dark:ztdl-border-divider-dark rounded-md px-4 text-sm ${className}`}
{...props}
>
<div className="ztdl-h-12 ztdl-p-[10px] ztdl-w-12 ztdl-flex ztdl-items-center ztdl-justify-center"> <div className="ztdl-h-12 ztdl-p-[10px] ztdl-w-12 ztdl-flex ztdl-items-center ztdl-justify-center">
<svg <svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21" className="w-full h-full">
xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 21 21"
className="w-full h-full"
>
<path fill="#f25022" d="M1 1H10V10H1z"></path> <path fill="#f25022" d="M1 1H10V10H1z"></path>
<path fill="#00a4ef" d="M1 11H10V20H1z"></path> <path fill="#00a4ef" d="M1 11H10V20H1z"></path>
<path fill="#7fba00" d="M11 1H20V10H11z"></path> <path fill="#7fba00" d="M11 1H20V10H11z"></path>
<path fill="#ffb900" d="M11 11H20V20H11z"></path> <path fill="#ffb900" d="M11 11H20V20H11z"></path>
</svg> </svg>
</div> </div>
{children ? ( {children ? children : <span className="ztdl-ml-4">{name ? name : "Sign in with AzureAD"}</span>}
children
) : (
<span className="ztdl-ml-4">
{name ? name : "Sign in with AzureAD"}
</span>
)}
</button> </button>
), ),
); );

View File

@@ -2,18 +2,11 @@
import { ReactNode, forwardRef } from "react"; import { ReactNode, forwardRef } from "react";
import { SignInWithIdentityProviderProps } from "./SignInWith"; import { SignInWithIdentityProviderProps } from "./SignInWith";
import { IdpButtonClasses } from "./classes";
export const SignInWithGithub = forwardRef< export const SignInWithGithub = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
HTMLButtonElement,
SignInWithIdentityProviderProps
>(
({ children, className = "", name = "", ...props }, ref): ReactNode => ( ({ children, className = "", name = "", ...props }, ref): ReactNode => (
<button <button type="button" ref={ref} className={`${IdpButtonClasses} ${className}`} {...props}>
type="button"
ref={ref}
className={`ztdl-h-[50px] ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center ztdl-bg-white ztdl-text-black dark:ztdl-bg-transparent dark:ztdl-text-white border ztdl-border-divider-light dark:ztdl-border-divider-dark rounded-md px-4 text-sm ${className}`}
{...props}
>
<div className="ztdl-h-8 ztdl-w-8 ztdl-mx-2 flex items-center justify-center"> <div className="ztdl-h-8 ztdl-w-8 ztdl-mx-2 flex items-center justify-center">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -46,11 +39,7 @@ export const SignInWithGithub = forwardRef<
></path> ></path>
</svg> </svg>
</div> </div>
{children ? ( {children ? children : <span className="ztdl-ml-4">{name ? name : "Sign in with GitHub"}</span>}
children
) : (
<span className="ztdl-ml-4">{name ? name : "Sign in with GitHub"}</span>
)}
</button> </button>
), ),
); );

View File

@@ -1,24 +1,12 @@
import { ReactNode, forwardRef } from "react"; import { ReactNode, forwardRef } from "react";
import { SignInWithIdentityProviderProps } from "./SignInWith"; import { SignInWithIdentityProviderProps } from "./SignInWith";
import { IdpButtonClasses } from "./classes";
export const SignInWithGitlab = forwardRef< export const SignInWithGitlab = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
HTMLButtonElement,
SignInWithIdentityProviderProps
>(
({ children, className = "", name = "", ...props }, ref): ReactNode => ( ({ children, className = "", name = "", ...props }, ref): ReactNode => (
<button <button type="button" ref={ref} className={`${IdpButtonClasses} ${className}`} {...props}>
type="button"
ref={ref}
className={`ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center ztdl-bg-white ztdl-text-black dark:ztdl-bg-transparent dark:ztdl-text-white border ztdl-border-divider-light dark:ztdl-border-divider-dark rounded-md px-4 text-sm ${className}`}
{...props}
>
<div className="ztdl-h-12 ztdl-w-12 flex items-center justify-center"> <div className="ztdl-h-12 ztdl-w-12 flex items-center justify-center">
<svg <svg xmlns="http://www.w3.org/2000/svg" width={25} height={24} fill="none">
xmlns="http://www.w3.org/2000/svg"
width={25}
height={24}
fill="none"
>
<path <path
fill="#e24329" fill="#e24329"
d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5z" d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5z"
@@ -37,11 +25,7 @@ export const SignInWithGitlab = forwardRef<
/> />
</svg> </svg>
</div> </div>
{children ? ( {children ? children : <span className="ztdl-ml-4">{name ? name : "Sign in with GitLab"}</span>}
children
) : (
<span className="ztdl-ml-4">{name ? name : "Sign in with GitLab"}</span>
)}
</button> </button>
), ),
); );

View File

@@ -1,24 +1,12 @@
import { ReactNode, forwardRef } from "react"; import { ReactNode, forwardRef } from "react";
import { SignInWithIdentityProviderProps } from "./SignInWith"; import { SignInWithIdentityProviderProps } from "./SignInWith";
import { IdpButtonClasses } from "./classes";
export const SignInWithGoogle = forwardRef< export const SignInWithGoogle = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
HTMLButtonElement,
SignInWithIdentityProviderProps
>(
({ children, className = "", name = "", ...props }, ref): ReactNode => ( ({ children, className = "", name = "", ...props }, ref): ReactNode => (
<button <button type="button" ref={ref} className={`${IdpButtonClasses} ${className}`} {...props}>
type="button"
ref={ref}
className={`ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center ztdl-bg-white ztdl-text-black dark:ztdl-bg-transparent dark:ztdl-text-white border ztdl-border-divider-light dark:ztdl-border-divider-dark rounded-md px-4 text-sm ${className}`}
{...props}
>
<div className="ztdl-h-12 ztdl-w-12 ztdl-flex ztdl-items-center ztdl-justify-center"> <div className="ztdl-h-12 ztdl-w-12 ztdl-flex ztdl-items-center ztdl-justify-center">
<svg <svg xmlns="http://www.w3.org/2000/svg" xmlSpace="preserve" id="Capa_1" viewBox="0 0 150 150">
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
id="Capa_1"
viewBox="0 0 150 150"
>
<style> <style>
{ {
".st0{fill:#1a73e8}.st1{fill:#ea4335}.st2{fill:#4285f4}.st3{fill:#fbbc04}.st4{fill:#34a853}.st5{fill:#4caf50}.st6{fill:#1e88e5}.st7{fill:#e53935}.st8{fill:#c62828}.st9{fill:#fbc02d}.st10{fill:#1565c0}.st11{fill:#2e7d32}.st16{clip-path:url(#SVGID_2_)}.st17{fill:#188038}.st18,.st19{opacity:.2;fill:#fff;enable-background:new}.st19{opacity:.3;fill:#0d652d}.st20{clip-path:url(#SVGID_4_)}.st21{opacity:.3;fill:url(#_45_shadow_1_);enable-background:new}.st22{clip-path:url(#SVGID_6_)}.st23{fill:#fa7b17}.st24,.st25,.st26{opacity:.3;fill:#174ea6;enable-background:new}.st25,.st26{fill:#a50e0e}.st26{fill:#e37400}.st27{fill:url(#Finish_mask_1_)}.st28{fill:#fff}.st29{fill:#0c9d58}.st30,.st31{opacity:.2;fill:#004d40;enable-background:new}.st31{fill:#3e2723}.st32{fill:#ffc107}.st33{fill:#1a237e;enable-background:new}.st33,.st34{opacity:.2}.st35{fill:#1a237e}.st36{fill:url(#SVGID_7_)}.st37{fill:#fbbc05}.st38{clip-path:url(#SVGID_9_);fill:#e53935}.st39{clip-path:url(#SVGID_11_);fill:#fbc02d}.st40{clip-path:url(#SVGID_13_);fill:#e53935}.st41{clip-path:url(#SVGID_15_);fill:#fbc02d}" ".st0{fill:#1a73e8}.st1{fill:#ea4335}.st2{fill:#4285f4}.st3{fill:#fbbc04}.st4{fill:#34a853}.st5{fill:#4caf50}.st6{fill:#1e88e5}.st7{fill:#e53935}.st8{fill:#c62828}.st9{fill:#fbc02d}.st10{fill:#1565c0}.st11{fill:#2e7d32}.st16{clip-path:url(#SVGID_2_)}.st17{fill:#188038}.st18,.st19{opacity:.2;fill:#fff;enable-background:new}.st19{opacity:.3;fill:#0d652d}.st20{clip-path:url(#SVGID_4_)}.st21{opacity:.3;fill:url(#_45_shadow_1_);enable-background:new}.st22{clip-path:url(#SVGID_6_)}.st23{fill:#fa7b17}.st24,.st25,.st26{opacity:.3;fill:#174ea6;enable-background:new}.st25,.st26{fill:#a50e0e}.st26{fill:#e37400}.st27{fill:url(#Finish_mask_1_)}.st28{fill:#fff}.st29{fill:#0c9d58}.st30,.st31{opacity:.2;fill:#004d40;enable-background:new}.st31{fill:#3e2723}.st32{fill:#ffc107}.st33{fill:#1a237e;enable-background:new}.st33,.st34{opacity:.2}.st35{fill:#1a237e}.st36{fill:url(#SVGID_7_)}.st37{fill:#fbbc05}.st38{clip-path:url(#SVGID_9_);fill:#e53935}.st39{clip-path:url(#SVGID_11_);fill:#fbc02d}.st40{clip-path:url(#SVGID_13_);fill:#e53935}.st41{clip-path:url(#SVGID_15_);fill:#fbc02d}"
@@ -50,11 +38,7 @@ export const SignInWithGoogle = forwardRef<
/> />
</svg> </svg>
</div> </div>
{children ? ( {children ? children : <span className="ztdl-ml-4">{name ? name : "Sign in with Google"}</span>}
children
) : (
<span className="ztdl-ml-4">{name ? name : "Sign in with Google"}</span>
)}
</button> </button>
), ),
); );

View File

@@ -0,0 +1,2 @@
export const IdpButtonClasses =
"transition-all ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center bg-background-light-400 text-text-light-500 dark:bg-background-dark-500 dark:text-text-dark-500 border border-divider-light hover:border-black dark:border-divider-dark hover:dark:border-white focus:border-primary-light-500 focus:dark:border-primary-dark-500 outline-none rounded-md px-4 text-sm";