mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 06:52:24 +00:00
Merge pull request #40 from zitadel/oidc
feat: oidc proxy, handle authRequest, callback
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
import { Session } from "@zitadel/server";
|
import { Session } from "@zitadel/server";
|
||||||
import { listSessions, server } from "#/lib/zitadel";
|
import { listSessions, server } from "#/lib/zitadel";
|
||||||
import { getAllSessionIds } from "#/utils/cookies";
|
import { getAllSessionCookieIds } from "#/utils/cookies";
|
||||||
import { UserPlusIcon } from "@heroicons/react/24/outline";
|
import { UserPlusIcon } from "@heroicons/react/24/outline";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import SessionsList from "#/ui/SessionsList";
|
import SessionsList from "#/ui/SessionsList";
|
||||||
|
|
||||||
async function loadSessions(): Promise<Session[]> {
|
async function loadSessions(): Promise<Session[]> {
|
||||||
const ids = await getAllSessionIds();
|
const ids = await getAllSessionCookieIds();
|
||||||
|
|
||||||
if (ids && ids.length) {
|
if (ids && ids.length) {
|
||||||
const response = await listSessions(
|
const response = await listSessions(
|
||||||
@@ -20,7 +20,13 @@ async function loadSessions(): Promise<Session[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
|
}) {
|
||||||
|
const authRequestId = searchParams?.authRequestId;
|
||||||
|
|
||||||
let sessions = await loadSessions();
|
let sessions = await loadSessions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -29,7 +35,7 @@ export default async function Page() {
|
|||||||
<p className="ztdl-p mb-6 block">Use your ZITADEL Account</p>
|
<p className="ztdl-p mb-6 block">Use your ZITADEL Account</p>
|
||||||
|
|
||||||
<div className="flex flex-col w-full space-y-2">
|
<div className="flex flex-col w-full space-y-2">
|
||||||
<SessionsList sessions={sessions} />
|
<SessionsList sessions={sessions} authRequestId={authRequestId} />
|
||||||
<Link href="/loginname">
|
<Link href="/loginname">
|
||||||
<div className="flex flex-row items-center py-3 px-4 hover:bg-black/10 dark:hover:bg-white/10 rounded-md transition-all">
|
<div className="flex flex-row items-center py-3 px-4 hover:bg-black/10 dark:hover:bg-white/10 rounded-md transition-all">
|
||||||
<div className="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
|
<div className="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
|
||||||
|
|||||||
122
apps/login/app/(login)/login/route.ts
Normal file
122
apps/login/app/(login)/login/route.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import {
|
||||||
|
createCallback,
|
||||||
|
getAuthRequest,
|
||||||
|
listSessions,
|
||||||
|
server,
|
||||||
|
} from "#/lib/zitadel";
|
||||||
|
import { SessionCookie, getAllSessions } from "#/utils/cookies";
|
||||||
|
import { Session, AuthRequest, Prompt } from "@zitadel/server";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
async function loadSessions(ids: string[]): Promise<Session[]> {
|
||||||
|
const response = await listSessions(
|
||||||
|
server,
|
||||||
|
ids.filter((id: string | undefined) => !!id)
|
||||||
|
);
|
||||||
|
return response?.sessions ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSession(
|
||||||
|
sessions: Session[],
|
||||||
|
authRequest: AuthRequest
|
||||||
|
): Session | undefined {
|
||||||
|
if (authRequest.hintUserId) {
|
||||||
|
console.log(`find session for hintUserId: ${authRequest.hintUserId}`);
|
||||||
|
return sessions.find((s) => s.factors?.user?.id === authRequest.hintUserId);
|
||||||
|
}
|
||||||
|
if (authRequest.loginHint) {
|
||||||
|
console.log(`find session for loginHint: ${authRequest.loginHint}`);
|
||||||
|
return sessions.find(
|
||||||
|
(s) => s.factors?.user?.loginName === authRequest.loginHint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const searchParams = request.nextUrl.searchParams;
|
||||||
|
const authRequestId = searchParams.get("authRequest");
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
const { authRequest } = await getAuthRequest(server, { authRequestId });
|
||||||
|
const sessionCookies: SessionCookie[] = await getAllSessions();
|
||||||
|
const ids = sessionCookies.map((s) => s.id);
|
||||||
|
|
||||||
|
let sessions: Session[] = [];
|
||||||
|
if (ids && ids.length) {
|
||||||
|
sessions = await loadSessions(ids);
|
||||||
|
} else {
|
||||||
|
console.info("No session cookie found.");
|
||||||
|
sessions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// use existing session and hydrate it for oidc
|
||||||
|
if (authRequest && sessions.length) {
|
||||||
|
// if some accounts are available for selection and select_account is set
|
||||||
|
if (
|
||||||
|
authRequest &&
|
||||||
|
authRequest.prompt.includes(Prompt.PROMPT_SELECT_ACCOUNT)
|
||||||
|
) {
|
||||||
|
const accountsUrl = new URL("/accounts", request.url);
|
||||||
|
if (authRequest?.id) {
|
||||||
|
accountsUrl.searchParams.set("authRequestId", authRequest?.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.redirect(accountsUrl);
|
||||||
|
} else {
|
||||||
|
// check for loginHint, userId hint sessions
|
||||||
|
let selectedSession = findSession(sessions, authRequest);
|
||||||
|
|
||||||
|
// if (!selectedSession) {
|
||||||
|
// selectedSession = sessions[0]; // TODO: remove
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (selectedSession && selectedSession.id) {
|
||||||
|
const cookie = sessionCookies.find(
|
||||||
|
(cookie) => cookie.id === selectedSession?.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cookie && cookie.id && cookie.token) {
|
||||||
|
const session = {
|
||||||
|
sessionId: cookie?.id,
|
||||||
|
sessionToken: cookie?.token,
|
||||||
|
};
|
||||||
|
const { callbackUrl } = await createCallback(server, {
|
||||||
|
authRequestId,
|
||||||
|
session,
|
||||||
|
});
|
||||||
|
return NextResponse.redirect(callbackUrl);
|
||||||
|
} else {
|
||||||
|
const accountsUrl = new URL("/accounts", request.url);
|
||||||
|
if (authRequest?.id) {
|
||||||
|
accountsUrl.searchParams.set("authRequestId", authRequest?.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.redirect(accountsUrl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const accountsUrl = new URL("/accounts", request.url);
|
||||||
|
if (authRequest?.id) {
|
||||||
|
accountsUrl.searchParams.set("authRequestId", authRequest?.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.redirect(accountsUrl);
|
||||||
|
// return NextResponse.error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const loginNameUrl = new URL("/loginname", request.url);
|
||||||
|
if (authRequest?.id) {
|
||||||
|
loginNameUrl.searchParams.set("authRequestId", authRequest?.id);
|
||||||
|
if (authRequest.loginHint) {
|
||||||
|
loginNameUrl.searchParams.set("loginName", authRequest.loginHint);
|
||||||
|
loginNameUrl.searchParams.set("submit", "true"); // autosubmit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.redirect(loginNameUrl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NextResponse.error();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ export default async function Page({
|
|||||||
searchParams: Record<string | number | symbol, string | undefined>;
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const loginName = searchParams?.loginName;
|
const loginName = searchParams?.loginName;
|
||||||
|
const authRequestId = searchParams?.authRequestId;
|
||||||
const submit: boolean = searchParams?.submit === "true";
|
const submit: boolean = searchParams?.submit === "true";
|
||||||
|
|
||||||
const loginSettings = await getLoginSettings(server);
|
const loginSettings = await getLoginSettings(server);
|
||||||
@@ -19,6 +20,7 @@ export default async function Page({
|
|||||||
<UsernameForm
|
<UsernameForm
|
||||||
loginSettings={loginSettings}
|
loginSettings={loginSettings}
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
|
authRequestId={authRequestId}
|
||||||
submit={submit}
|
submit={submit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default async function Page({
|
|||||||
}: {
|
}: {
|
||||||
searchParams: Record<string | number | symbol, string | undefined>;
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const { loginName, altPassword } = searchParams;
|
const { loginName, altPassword, authRequestId } = searchParams;
|
||||||
|
|
||||||
const sessionFactors = await loadSession(loginName);
|
const sessionFactors = await loadSession(loginName);
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ export default async function Page({
|
|||||||
{loginName && (
|
{loginName && (
|
||||||
<LoginPasskey
|
<LoginPasskey
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
|
authRequestId={authRequestId}
|
||||||
altPassword={altPassword === "true"}
|
altPassword={altPassword === "true"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default async function Page({
|
|||||||
}: {
|
}: {
|
||||||
searchParams: Record<string | number | symbol, string | undefined>;
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const { loginName, promptPasswordless, alt } = searchParams;
|
const { loginName, promptPasswordless, authRequestId, alt } = searchParams;
|
||||||
const sessionFactors = await loadSession(loginName);
|
const sessionFactors = await loadSession(loginName);
|
||||||
|
|
||||||
async function loadSession(loginName?: string) {
|
async function loadSession(loginName?: string) {
|
||||||
@@ -46,6 +46,7 @@ export default async function Page({
|
|||||||
|
|
||||||
<PasswordForm
|
<PasswordForm
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
|
authRequestId={authRequestId}
|
||||||
promptPasswordless={promptPasswordless === "true"}
|
promptPasswordless={promptPasswordless === "true"}
|
||||||
isAlternative={alt === "true"}
|
isAlternative={alt === "true"}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { ProviderSlug } from "#/lib/demos";
|
import { ProviderSlug } from "#/lib/demos";
|
||||||
import { addHumanUser, server } from "#/lib/zitadel";
|
import { server } from "#/lib/zitadel";
|
||||||
import Alert, { AlertType } from "#/ui/Alert";
|
import Alert, { AlertType } from "#/ui/Alert";
|
||||||
import {
|
import {
|
||||||
AddHumanUserRequest,
|
AddHumanUserRequest,
|
||||||
IDPInformation,
|
IDPInformation,
|
||||||
RetrieveIdentityProviderInformationResponse,
|
RetrieveIdentityProviderIntentResponse,
|
||||||
user,
|
user,
|
||||||
IDPLink,
|
IDPLink,
|
||||||
} from "@zitadel/server";
|
} from "@zitadel/server";
|
||||||
@@ -27,8 +27,8 @@ const PROVIDER_MAPPING: {
|
|||||||
// organisation: Organisation | undefined;
|
// organisation: Organisation | undefined;
|
||||||
profile: {
|
profile: {
|
||||||
displayName: idp.rawInformation?.User?.name ?? "",
|
displayName: idp.rawInformation?.User?.name ?? "",
|
||||||
firstName: idp.rawInformation?.User?.given_name ?? "",
|
givenName: idp.rawInformation?.User?.given_name ?? "",
|
||||||
lastName: idp.rawInformation?.User?.family_name ?? "",
|
familyName: idp.rawInformation?.User?.family_name ?? "",
|
||||||
},
|
},
|
||||||
idpLinks: [idpLink],
|
idpLinks: [idpLink],
|
||||||
};
|
};
|
||||||
@@ -49,8 +49,8 @@ const PROVIDER_MAPPING: {
|
|||||||
// organisation: Organisation | undefined;
|
// organisation: Organisation | undefined;
|
||||||
profile: {
|
profile: {
|
||||||
displayName: idp.rawInformation?.name ?? "",
|
displayName: idp.rawInformation?.name ?? "",
|
||||||
firstName: idp.rawInformation?.name ?? "",
|
givenName: idp.rawInformation?.name ?? "",
|
||||||
lastName: idp.rawInformation?.name ?? "",
|
familyName: idp.rawInformation?.name ?? "",
|
||||||
},
|
},
|
||||||
idpLinks: [idpLink],
|
idpLinks: [idpLink],
|
||||||
};
|
};
|
||||||
@@ -64,8 +64,11 @@ function retrieveIDP(
|
|||||||
): Promise<IDPInformation | undefined> {
|
): Promise<IDPInformation | undefined> {
|
||||||
const userService = user.getUser(server);
|
const userService = user.getUser(server);
|
||||||
return userService
|
return userService
|
||||||
.retrieveIdentityProviderInformation({ intentId: id, token: token }, {})
|
.retrieveIdentityProviderIntent(
|
||||||
.then((resp: RetrieveIdentityProviderInformationResponse) => {
|
{ idpIntentId: id, idpIntentToken: token },
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
.then((resp: RetrieveIdentityProviderIntentResponse) => {
|
||||||
return resp.idpInformation;
|
return resp.idpInformation;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
import { getSession, server } from "#/lib/zitadel";
|
import { createCallback, getSession, server } from "#/lib/zitadel";
|
||||||
import UserAvatar from "#/ui/UserAvatar";
|
import UserAvatar from "#/ui/UserAvatar";
|
||||||
import { getMostRecentCookieWithLoginname } from "#/utils/cookies";
|
import { getMostRecentCookieWithLoginname } from "#/utils/cookies";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
async function loadSession(loginName: string) {
|
async function loadSession(loginName: string, authRequestId?: string) {
|
||||||
const recent = await getMostRecentCookieWithLoginname(`${loginName}`);
|
const recent = await getMostRecentCookieWithLoginname(`${loginName}`);
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
return createCallback(server, {
|
||||||
|
authRequestId,
|
||||||
|
session: { sessionId: recent.id, sessionToken: recent.token },
|
||||||
|
}).then(({ callbackUrl }) => {
|
||||||
|
return redirect(callbackUrl);
|
||||||
|
});
|
||||||
|
}
|
||||||
return getSession(server, recent.id, recent.token).then((response) => {
|
return getSession(server, recent.id, recent.token).then((response) => {
|
||||||
if (response?.session) {
|
if (response?.session) {
|
||||||
return response.session;
|
return response.session;
|
||||||
@@ -13,8 +22,8 @@ async function loadSession(loginName: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({ searchParams }: { searchParams: any }) {
|
export default async function Page({ searchParams }: { searchParams: any }) {
|
||||||
const { loginName } = searchParams;
|
const { loginName, authRequestId } = searchParams;
|
||||||
const sessionFactors = await loadSession(loginName);
|
const sessionFactors = await loadSession(loginName, authRequestId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ export async function POST(request: NextRequest) {
|
|||||||
if (body) {
|
if (body) {
|
||||||
let { idpId, successUrl, failureUrl } = body;
|
let { idpId, successUrl, failureUrl } = body;
|
||||||
|
|
||||||
return startIdentityProviderFlow(server, { idpId, successUrl, failureUrl })
|
return startIdentityProviderFlow(server, {
|
||||||
|
idpId,
|
||||||
|
urls: {
|
||||||
|
successUrl,
|
||||||
|
failureUrl,
|
||||||
|
},
|
||||||
|
})
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
return NextResponse.json(resp);
|
return NextResponse.json(resp);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,53 +1,18 @@
|
|||||||
import {
|
import { listAuthenticationMethodTypes } from "#/lib/zitadel";
|
||||||
getSession,
|
|
||||||
listAuthenticationMethodTypes,
|
|
||||||
server,
|
|
||||||
} from "#/lib/zitadel";
|
|
||||||
import { getSessionCookieById } from "#/utils/cookies";
|
|
||||||
import { createSessionAndUpdateCookie } from "#/utils/session";
|
import { createSessionAndUpdateCookie } from "#/utils/session";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
|
||||||
const { searchParams } = new URL(request.url);
|
|
||||||
const sessionId = searchParams.get("sessionId");
|
|
||||||
if (sessionId) {
|
|
||||||
const sessionCookie = await getSessionCookieById(sessionId);
|
|
||||||
|
|
||||||
const session = await getSession(
|
|
||||||
server,
|
|
||||||
sessionCookie.id,
|
|
||||||
sessionCookie.token
|
|
||||||
);
|
|
||||||
|
|
||||||
const userId = session?.session?.factors?.user?.id;
|
|
||||||
|
|
||||||
if (userId) {
|
|
||||||
return listAuthenticationMethodTypes(userId)
|
|
||||||
.then((methods) => {
|
|
||||||
return NextResponse.json(methods);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
return NextResponse.json(error, { status: 500 });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ details: "could not get session" },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return NextResponse.json({}, { status: 400 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
if (body) {
|
if (body) {
|
||||||
const { loginName } = body;
|
const { loginName, authRequestId } = body;
|
||||||
|
|
||||||
const domain: string = request.nextUrl.hostname;
|
return createSessionAndUpdateCookie(
|
||||||
|
loginName,
|
||||||
return createSessionAndUpdateCookie(loginName, undefined, domain, undefined)
|
undefined,
|
||||||
|
undefined,
|
||||||
|
authRequestId
|
||||||
|
)
|
||||||
.then((session) => {
|
.then((session) => {
|
||||||
if (session.factors?.user?.id) {
|
if (session.factors?.user?.id) {
|
||||||
return listAuthenticationMethodTypes(session.factors?.user?.id)
|
return listAuthenticationMethodTypes(session.factors?.user?.id)
|
||||||
@@ -62,16 +27,11 @@ export async function POST(request: NextRequest) {
|
|||||||
return NextResponse.json(error, { status: 500 });
|
return NextResponse.json(error, { status: 500 });
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw "No user id found in session";
|
throw { details: "No user id found in session" };
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
return NextResponse.json(
|
return NextResponse.json(error, { status: 500 });
|
||||||
{
|
|
||||||
details: "could not add session to cookie",
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return NextResponse.error();
|
return NextResponse.error();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
createSessionAndUpdateCookie,
|
createSessionAndUpdateCookie,
|
||||||
setSessionAndUpdateCookie,
|
setSessionAndUpdateCookie,
|
||||||
} from "#/utils/session";
|
} from "#/utils/session";
|
||||||
|
import { RequestChallenges } from "@zitadel/server";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
@@ -17,12 +18,10 @@ export async function POST(request: NextRequest) {
|
|||||||
if (body) {
|
if (body) {
|
||||||
const { loginName, password } = body;
|
const { loginName, password } = body;
|
||||||
|
|
||||||
const domain: string = request.nextUrl.hostname;
|
|
||||||
|
|
||||||
return createSessionAndUpdateCookie(
|
return createSessionAndUpdateCookie(
|
||||||
loginName,
|
loginName,
|
||||||
password,
|
password,
|
||||||
domain,
|
undefined,
|
||||||
undefined
|
undefined
|
||||||
).then((session) => {
|
).then((session) => {
|
||||||
return NextResponse.json(session);
|
return NextResponse.json(session);
|
||||||
@@ -44,7 +43,8 @@ export async function PUT(request: NextRequest) {
|
|||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|
||||||
if (body) {
|
if (body) {
|
||||||
const { loginName, password, challenges, passkey } = body;
|
const { loginName, password, webAuthN, authRequestId } = body;
|
||||||
|
const challenges: RequestChallenges = body.challenges;
|
||||||
|
|
||||||
const recentPromise: Promise<SessionCookie> = loginName
|
const recentPromise: Promise<SessionCookie> = loginName
|
||||||
? getSessionCookieByLoginName(loginName).catch((error) => {
|
? getSessionCookieByLoginName(loginName).catch((error) => {
|
||||||
@@ -56,16 +56,21 @@ export async function PUT(request: NextRequest) {
|
|||||||
|
|
||||||
const domain: string = request.nextUrl.hostname;
|
const domain: string = request.nextUrl.hostname;
|
||||||
|
|
||||||
|
if (challenges && challenges.webAuthN && !challenges.webAuthN.domain) {
|
||||||
|
challenges.webAuthN.domain = domain;
|
||||||
|
}
|
||||||
|
|
||||||
return recentPromise
|
return recentPromise
|
||||||
.then((recent) => {
|
.then((recent) => {
|
||||||
|
console.log("setsession", webAuthN);
|
||||||
return setSessionAndUpdateCookie(
|
return setSessionAndUpdateCookie(
|
||||||
recent.id,
|
recent.id,
|
||||||
recent.token,
|
recent.token,
|
||||||
recent.loginName,
|
recent.loginName,
|
||||||
password,
|
password,
|
||||||
passkey,
|
webAuthN,
|
||||||
domain,
|
challenges,
|
||||||
challenges
|
authRequestId
|
||||||
).then((session) => {
|
).then((session) => {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { stub } from "../support/mock";
|
|||||||
|
|
||||||
describe("login", () => {
|
describe("login", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stub("zitadel.session.v2alpha.SessionService", "CreateSession", {
|
stub("zitadel.session.v2beta.SessionService", "CreateSession", {
|
||||||
data: {
|
data: {
|
||||||
details: {
|
details: {
|
||||||
sequence: 859,
|
sequence: 859,
|
||||||
@@ -16,7 +16,7 @@ describe("login", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
stub("zitadel.session.v2alpha.SessionService", "GetSession", {
|
stub("zitadel.session.v2beta.SessionService", "GetSession", {
|
||||||
data: {
|
data: {
|
||||||
session: {
|
session: {
|
||||||
id: "221394658884845598",
|
id: "221394658884845598",
|
||||||
@@ -29,16 +29,15 @@ describe("login", () => {
|
|||||||
loginName: "john@zitadel.com",
|
loginName: "john@zitadel.com",
|
||||||
},
|
},
|
||||||
password: undefined,
|
password: undefined,
|
||||||
passkey: undefined,
|
webAuthN: undefined,
|
||||||
intent: undefined,
|
intent: undefined,
|
||||||
},
|
},
|
||||||
metadata: {},
|
metadata: {},
|
||||||
domain: "localhost",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
stub("zitadel.settings.v2alpha.SettingsService", "GetLoginSettings", {
|
stub("zitadel.settings.v2beta.SettingsService", "GetLoginSettings", {
|
||||||
data: {
|
data: {
|
||||||
settings: {
|
settings: {
|
||||||
passkeysType: 1,
|
passkeysType: 1,
|
||||||
@@ -48,23 +47,19 @@ describe("login", () => {
|
|||||||
});
|
});
|
||||||
describe("password login", () => {
|
describe("password login", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stub(
|
stub("zitadel.user.v2beta.UserService", "ListAuthenticationMethodTypes", {
|
||||||
"zitadel.user.v2alpha.UserService",
|
data: {
|
||||||
"ListAuthenticationMethodTypes",
|
authMethodTypes: [1], // 1 for password authentication
|
||||||
{
|
},
|
||||||
data: {
|
});
|
||||||
authMethodTypes: [1], // 1 for password authentication
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it("should redirect a user with password authentication to /password", () => {
|
it("should redirect a user with password authentication to /password", () => {
|
||||||
cy.visit("/loginname?loginName=johndoe%40zitadel.com&submit=true");
|
cy.visit("/loginname?loginName=john%40zitadel.com&submit=true");
|
||||||
cy.location("pathname", { timeout: 10_000 }).should("eq", "/password");
|
cy.location("pathname", { timeout: 10_000 }).should("eq", "/password");
|
||||||
});
|
});
|
||||||
describe("with passkey prompt", () => {
|
describe("with passkey prompt", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stub("zitadel.session.v2alpha.SessionService", "SetSession", {
|
stub("zitadel.session.v2beta.SessionService", "SetSession", {
|
||||||
data: {
|
data: {
|
||||||
details: {
|
details: {
|
||||||
sequence: 859,
|
sequence: 859,
|
||||||
@@ -91,18 +86,14 @@ describe("login", () => {
|
|||||||
});
|
});
|
||||||
describe("passkey login", () => {
|
describe("passkey login", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stub(
|
stub("zitadel.user.v2beta.UserService", "ListAuthenticationMethodTypes", {
|
||||||
"zitadel.user.v2alpha.UserService",
|
data: {
|
||||||
"ListAuthenticationMethodTypes",
|
authMethodTypes: [2], // 2 for passwordless authentication
|
||||||
{
|
},
|
||||||
data: {
|
});
|
||||||
authMethodTypes: [2], // 2 for passwordless authentication
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it("should redirect a user with passwordless authentication to /passkey/login", () => {
|
it("should redirect a user with passwordless authentication to /passkey/login", () => {
|
||||||
cy.visit("/loginname?loginName=johndoe%40zitadel.com&submit=true");
|
cy.visit("/loginname?loginName=john%40zitadel.com&submit=true");
|
||||||
cy.location("pathname", { timeout: 10_000 }).should(
|
cy.location("pathname", { timeout: 10_000 }).should(
|
||||||
"eq",
|
"eq",
|
||||||
"/passkey/login"
|
"/passkey/login"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const IDP_URL = "https://example.com/idp/url";
|
|||||||
|
|
||||||
describe("register idps", () => {
|
describe("register idps", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stub("zitadel.user.v2alpha.UserService", "StartIdentityProviderFlow", {
|
stub("zitadel.user.v2beta.UserService", "StartIdentityProviderIntent", {
|
||||||
data: {
|
data: {
|
||||||
authUrl: IDP_URL,
|
authUrl: IDP_URL,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { stub } from "../support/mock";
|
|||||||
|
|
||||||
describe("register", () => {
|
describe("register", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stub("zitadel.user.v2alpha.UserService", "AddHumanUser", {
|
stub("zitadel.user.v2beta.UserService", "AddHumanUser", {
|
||||||
data: {
|
data: {
|
||||||
userId: "123",
|
userId: "123",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { stub } from "../support/mock";
|
|||||||
|
|
||||||
describe("/verify", () => {
|
describe("/verify", () => {
|
||||||
it("redirects after successful email verification", () => {
|
it("redirects after successful email verification", () => {
|
||||||
stub("zitadel.user.v2alpha.UserService", "VerifyEmail");
|
stub("zitadel.user.v2beta.UserService", "VerifyEmail");
|
||||||
cy.visit("/verify?userID=123&code=abc&submit=true");
|
cy.visit("/verify?userID=123&code=abc&submit=true");
|
||||||
cy.location("pathname", { timeout: 10_000 }).should("eq", "/loginname");
|
cy.location("pathname", { timeout: 10_000 }).should("eq", "/loginname");
|
||||||
});
|
});
|
||||||
it("shows an error if validation failed", () => {
|
it("shows an error if validation failed", () => {
|
||||||
stub("zitadel.user.v2alpha.UserService", "VerifyEmail", {
|
stub("zitadel.user.v2beta.UserService", "VerifyEmail", {
|
||||||
code: 3,
|
code: 3,
|
||||||
error: "error validating code",
|
error: "error validating code",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
ZitadelServer,
|
ZitadelServer,
|
||||||
ZitadelServerOptions,
|
ZitadelServerOptions,
|
||||||
user,
|
user,
|
||||||
|
oidc,
|
||||||
settings,
|
settings,
|
||||||
getServers,
|
getServers,
|
||||||
initializeServer,
|
initializeServer,
|
||||||
@@ -19,16 +20,22 @@ import {
|
|||||||
GetSessionResponse,
|
GetSessionResponse,
|
||||||
VerifyEmailResponse,
|
VerifyEmailResponse,
|
||||||
SetSessionResponse,
|
SetSessionResponse,
|
||||||
|
SetSessionRequest,
|
||||||
DeleteSessionResponse,
|
DeleteSessionResponse,
|
||||||
VerifyPasskeyRegistrationResponse,
|
VerifyPasskeyRegistrationResponse,
|
||||||
ChallengeKind,
|
|
||||||
LoginSettings,
|
LoginSettings,
|
||||||
GetLoginSettingsResponse,
|
GetLoginSettingsResponse,
|
||||||
ListAuthenticationMethodTypesResponse,
|
ListAuthenticationMethodTypesResponse,
|
||||||
StartIdentityProviderFlowRequest,
|
StartIdentityProviderIntentRequest,
|
||||||
StartIdentityProviderFlowResponse,
|
StartIdentityProviderIntentResponse,
|
||||||
RetrieveIdentityProviderInformationRequest,
|
RetrieveIdentityProviderIntentRequest,
|
||||||
RetrieveIdentityProviderInformationResponse,
|
RetrieveIdentityProviderIntentResponse,
|
||||||
|
GetAuthRequestResponse,
|
||||||
|
GetAuthRequestRequest,
|
||||||
|
CreateCallbackRequest,
|
||||||
|
CreateCallbackResponse,
|
||||||
|
RequestChallenges,
|
||||||
|
AddHumanUserRequest,
|
||||||
} from "@zitadel/server";
|
} from "@zitadel/server";
|
||||||
|
|
||||||
export const zitadelConfig: ZitadelServerOptions = {
|
export const zitadelConfig: ZitadelServerOptions = {
|
||||||
@@ -95,9 +102,8 @@ export async function getPasswordComplexitySettings(
|
|||||||
export async function createSession(
|
export async function createSession(
|
||||||
server: ZitadelServer,
|
server: ZitadelServer,
|
||||||
loginName: string,
|
loginName: string,
|
||||||
domain: string,
|
|
||||||
password: string | undefined,
|
password: string | undefined,
|
||||||
challenges: ChallengeKind[] | undefined
|
challenges: RequestChallenges | undefined
|
||||||
): Promise<CreateSessionResponse | undefined> {
|
): Promise<CreateSessionResponse | undefined> {
|
||||||
const sessionService = session.getSession(server);
|
const sessionService = session.getSession(server);
|
||||||
return password
|
return password
|
||||||
@@ -105,12 +111,12 @@ export async function createSession(
|
|||||||
{
|
{
|
||||||
checks: { user: { loginName }, password: { password } },
|
checks: { user: { loginName }, password: { password } },
|
||||||
challenges,
|
challenges,
|
||||||
domain,
|
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
: sessionService.createSession(
|
: sessionService.createSession(
|
||||||
{ checks: { user: { loginName } }, domain },
|
{ checks: { user: { loginName } }, challenges },
|
||||||
|
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -119,23 +125,29 @@ export async function setSession(
|
|||||||
server: ZitadelServer,
|
server: ZitadelServer,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
sessionToken: string,
|
sessionToken: string,
|
||||||
domain: string | undefined,
|
|
||||||
password: string | undefined,
|
password: string | undefined,
|
||||||
passkey: { credentialAssertionData: any } | undefined,
|
webAuthN: { credentialAssertionData: any } | undefined,
|
||||||
challenges: ChallengeKind[] | undefined
|
challenges: RequestChallenges | undefined
|
||||||
): Promise<SetSessionResponse | undefined> {
|
): Promise<SetSessionResponse | undefined> {
|
||||||
const sessionService = session.getSession(server);
|
const sessionService = session.getSession(server);
|
||||||
|
|
||||||
const payload = { sessionId, sessionToken, challenges, domain };
|
const payload: SetSessionRequest = {
|
||||||
return password
|
sessionId,
|
||||||
? sessionService.setSession(
|
sessionToken,
|
||||||
{
|
challenges,
|
||||||
...payload,
|
checks: {},
|
||||||
checks: { password: { password }, passkey },
|
metadata: {},
|
||||||
},
|
};
|
||||||
{}
|
|
||||||
)
|
if (password && payload.checks) {
|
||||||
: sessionService.setSession(payload, {});
|
payload.checks.password = { password };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webAuthN && payload.checks) {
|
||||||
|
payload.checks.webAuthN = webAuthN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionService.setSession(payload, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSession(
|
export async function getSession(
|
||||||
@@ -179,10 +191,10 @@ export async function addHumanUser(
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const userService = user.getUser(server);
|
const userService = user.getUser(server);
|
||||||
|
|
||||||
const payload = {
|
const payload: Partial<AddHumanUserRequest> = {
|
||||||
email: { email },
|
email: { email },
|
||||||
username: email,
|
username: email,
|
||||||
profile: { firstName, lastName },
|
profile: { givenName: firstName, familyName: lastName },
|
||||||
};
|
};
|
||||||
return userService
|
return userService
|
||||||
.addHumanUser(
|
.addHumanUser(
|
||||||
@@ -201,29 +213,48 @@ export async function addHumanUser(
|
|||||||
|
|
||||||
export async function startIdentityProviderFlow(
|
export async function startIdentityProviderFlow(
|
||||||
server: ZitadelServer,
|
server: ZitadelServer,
|
||||||
{ idpId, successUrl, failureUrl }: StartIdentityProviderFlowRequest
|
{ idpId, urls }: StartIdentityProviderIntentRequest
|
||||||
): Promise<StartIdentityProviderFlowResponse> {
|
): Promise<StartIdentityProviderIntentResponse> {
|
||||||
const userService = user.getUser(server);
|
const userService = user.getUser(server);
|
||||||
|
|
||||||
return userService.startIdentityProviderFlow({
|
return userService.startIdentityProviderIntent({
|
||||||
idpId,
|
idpId,
|
||||||
successUrl,
|
urls,
|
||||||
failureUrl,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function retrieveIdentityProviderInformation(
|
export async function retrieveIdentityProviderInformation(
|
||||||
server: ZitadelServer,
|
server: ZitadelServer,
|
||||||
{ intentId, token }: RetrieveIdentityProviderInformationRequest
|
{ idpIntentId, idpIntentToken }: RetrieveIdentityProviderIntentRequest
|
||||||
): Promise<RetrieveIdentityProviderInformationResponse> {
|
): Promise<RetrieveIdentityProviderIntentResponse> {
|
||||||
const userService = user.getUser(server);
|
const userService = user.getUser(server);
|
||||||
|
|
||||||
return userService.retrieveIdentityProviderInformation({
|
return userService.retrieveIdentityProviderIntent({
|
||||||
intentId,
|
idpIntentId,
|
||||||
token,
|
idpIntentToken,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAuthRequest(
|
||||||
|
server: ZitadelServer,
|
||||||
|
{ authRequestId }: GetAuthRequestRequest
|
||||||
|
): Promise<GetAuthRequestResponse> {
|
||||||
|
const oidcService = oidc.getOidc(server);
|
||||||
|
|
||||||
|
return oidcService.getAuthRequest({
|
||||||
|
authRequestId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createCallback(
|
||||||
|
server: ZitadelServer,
|
||||||
|
req: CreateCallbackRequest
|
||||||
|
): Promise<CreateCallbackResponse> {
|
||||||
|
const oidcService = oidc.getOidc(server);
|
||||||
|
|
||||||
|
return oidcService.createCallback(req);
|
||||||
|
}
|
||||||
|
|
||||||
export async function verifyEmail(
|
export async function verifyEmail(
|
||||||
server: ZitadelServer,
|
server: ZitadelServer,
|
||||||
userId: string,
|
userId: string,
|
||||||
|
|||||||
28
apps/login/middleware.ts
Normal file
28
apps/login/middleware.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import type { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ["/.well-known/:path*", "/oauth/:path*", "/oidc/:path*"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const INSTANCE = process.env.ZITADEL_API_URL;
|
||||||
|
const SERVICE_USER_ID = process.env.ZITADEL_SERVICE_USER_ID as string;
|
||||||
|
|
||||||
|
export function middleware(request: NextRequest) {
|
||||||
|
const requestHeaders = new Headers(request.headers);
|
||||||
|
requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID);
|
||||||
|
|
||||||
|
requestHeaders.set("Forwarded", `host="${request.nextUrl.host}"`);
|
||||||
|
|
||||||
|
const responseHeaders = new Headers();
|
||||||
|
responseHeaders.set("Access-Control-Allow-Origin", "*");
|
||||||
|
responseHeaders.set("Access-Control-Allow-Headers", "*");
|
||||||
|
|
||||||
|
request.nextUrl.href = `${INSTANCE}${request.nextUrl.pathname}${request.nextUrl.search}`;
|
||||||
|
return NextResponse.rewrite(request.nextUrl, {
|
||||||
|
request: {
|
||||||
|
headers: requestHeaders,
|
||||||
|
},
|
||||||
|
headers: responseHeaders,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"service": "zitadel.settings.v2alpha.SettingsService",
|
"service": "zitadel.settings.v2beta.SettingsService",
|
||||||
"method": "GetBrandingSettings",
|
"method": "GetBrandingSettings",
|
||||||
"out": {}
|
"out": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"service": "zitadel.settings.v2alpha.SettingsService",
|
"service": "zitadel.settings.v2beta.SettingsService",
|
||||||
"method": "GetLegalAndSupportSettings",
|
"method": "GetLegalAndSupportSettings",
|
||||||
"out": {
|
"out": {
|
||||||
"data": {
|
"data": {
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"service": "zitadel.settings.v2alpha.SettingsService",
|
"service": "zitadel.settings.v2beta.SettingsService",
|
||||||
"method": "GetActiveIdentityProviders",
|
"method": "GetActiveIdentityProviders",
|
||||||
"out": {
|
"out": {
|
||||||
"data": {
|
"data": {
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"service": "zitadel.settings.v2alpha.SettingsService",
|
"service": "zitadel.settings.v2beta.SettingsService",
|
||||||
"method": "GetPasswordComplexitySettings",
|
"method": "GetPasswordComplexitySettings",
|
||||||
"out": {
|
"out": {
|
||||||
"data": {
|
"data": {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
zitadel/user/v2alpha/user_service.proto
|
zitadel/user/v2beta/user_service.proto
|
||||||
zitadel/session/v2alpha/session_service.proto
|
zitadel/session/v2beta/session_service.proto
|
||||||
zitadel/settings/v2alpha/settings_service.proto
|
zitadel/settings/v2beta/settings_service.proto
|
||||||
zitadel/management.proto
|
zitadel/management.proto
|
||||||
zitadel/auth.proto
|
zitadel/auth.proto
|
||||||
zitadel/admin.proto
|
zitadel/admin.proto
|
||||||
@@ -40,7 +40,6 @@
|
|||||||
"@zitadel/react": "workspace:*",
|
"@zitadel/react": "workspace:*",
|
||||||
"@zitadel/server": "workspace:*",
|
"@zitadel/server": "workspace:*",
|
||||||
"clsx": "1.2.1",
|
"clsx": "1.2.1",
|
||||||
"date-fns": "2.29.3",
|
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"next": "13.4.12",
|
"next": "13.4.12",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Challenges_Passkey } from "@zitadel/server";
|
|
||||||
import { coerceToArrayBuffer, coerceToBase64Url } from "#/utils/base64";
|
import { coerceToArrayBuffer, coerceToBase64Url } from "#/utils/base64";
|
||||||
import { Button, ButtonVariants } from "./Button";
|
import { Button, ButtonVariants } from "./Button";
|
||||||
import Alert from "./Alert";
|
import Alert from "./Alert";
|
||||||
@@ -10,10 +9,15 @@ import { Spinner } from "./Spinner";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loginName: string;
|
loginName: string;
|
||||||
|
authRequestId?: string;
|
||||||
altPassword: boolean;
|
altPassword: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LoginPasskey({ loginName, altPassword }: Props) {
|
export default function LoginPasskey({
|
||||||
|
loginName,
|
||||||
|
authRequestId,
|
||||||
|
altPassword,
|
||||||
|
}: Props) {
|
||||||
const [error, setError] = useState<string>("");
|
const [error, setError] = useState<string>("");
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -28,7 +32,7 @@ export default function LoginPasskey({ loginName, altPassword }: Props) {
|
|||||||
updateSessionForChallenge()
|
updateSessionForChallenge()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const pK =
|
const pK =
|
||||||
response.challenges.passkey.publicKeyCredentialRequestOptions
|
response.challenges.webAuthN.publicKeyCredentialRequestOptions
|
||||||
.publicKey;
|
.publicKey;
|
||||||
if (pK) {
|
if (pK) {
|
||||||
submitLoginAndContinue(pK)
|
submitLoginAndContinue(pK)
|
||||||
@@ -60,7 +64,13 @@ export default function LoginPasskey({ loginName, altPassword }: Props) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName,
|
loginName,
|
||||||
challenges: [1], // request passkey challenge
|
challenges: {
|
||||||
|
webAuthN: {
|
||||||
|
domain: "",
|
||||||
|
userVerificationRequirement: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authRequestId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,7 +91,8 @@ export default function LoginPasskey({ loginName, altPassword }: Props) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName,
|
loginName,
|
||||||
passkey: data,
|
webAuthN: { credentialAssertionData: data },
|
||||||
|
authRequestId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -115,18 +126,18 @@ export default function LoginPasskey({ loginName, altPassword }: Props) {
|
|||||||
})
|
})
|
||||||
.then((assertedCredential: any) => {
|
.then((assertedCredential: any) => {
|
||||||
if (assertedCredential) {
|
if (assertedCredential) {
|
||||||
let authData = new Uint8Array(
|
const authData = new Uint8Array(
|
||||||
assertedCredential.response.authenticatorData
|
assertedCredential.response.authenticatorData
|
||||||
);
|
);
|
||||||
let clientDataJSON = new Uint8Array(
|
const clientDataJSON = new Uint8Array(
|
||||||
assertedCredential.response.clientDataJSON
|
assertedCredential.response.clientDataJSON
|
||||||
);
|
);
|
||||||
let rawId = new Uint8Array(assertedCredential.rawId);
|
const rawId = new Uint8Array(assertedCredential.rawId);
|
||||||
let sig = new Uint8Array(assertedCredential.response.signature);
|
const sig = new Uint8Array(assertedCredential.response.signature);
|
||||||
let userHandle = new Uint8Array(
|
const userHandle = new Uint8Array(
|
||||||
assertedCredential.response.userHandle
|
assertedCredential.response.userHandle
|
||||||
);
|
);
|
||||||
let data = JSON.stringify({
|
const data = {
|
||||||
id: assertedCredential.id,
|
id: assertedCredential.id,
|
||||||
rawId: coerceToBase64Url(rawId, "rawId"),
|
rawId: coerceToBase64Url(rawId, "rawId"),
|
||||||
type: assertedCredential.type,
|
type: assertedCredential.type,
|
||||||
@@ -139,9 +150,21 @@ export default function LoginPasskey({ loginName, altPassword }: Props) {
|
|||||||
signature: coerceToBase64Url(sig, "sig"),
|
signature: coerceToBase64Url(sig, "sig"),
|
||||||
userHandle: coerceToBase64Url(userHandle, "userHandle"),
|
userHandle: coerceToBase64Url(userHandle, "userHandle"),
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
return submitLogin(data).then(() => {
|
return submitLogin(data).then((resp) => {
|
||||||
return router.push(`/accounts`);
|
return router.push(
|
||||||
|
`/signedin?` +
|
||||||
|
new URLSearchParams(
|
||||||
|
authRequestId
|
||||||
|
? {
|
||||||
|
loginName: resp.factors.user.loginName,
|
||||||
|
authRequestId,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
loginName: resp.factors.user.loginName,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -169,11 +192,16 @@ export default function LoginPasskey({ loginName, altPassword }: Props) {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={ButtonVariants.Secondary}
|
variant={ButtonVariants.Secondary}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
router.push(
|
const params = { loginName, alt: "true" };
|
||||||
"/password?" + new URLSearchParams({ loginName, alt: "true" }) // alt is set because password is requested as alternative auth method, so passwordless prompt can be escaped
|
|
||||||
)
|
return router.push(
|
||||||
}
|
"/password?" +
|
||||||
|
new URLSearchParams(
|
||||||
|
authRequestId ? { ...params, authRequestId } : params
|
||||||
|
) // alt is set because password is requested as alternative auth method, so passwordless prompt can be escaped
|
||||||
|
);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
use password
|
use password
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ type Inputs = {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loginName?: string;
|
loginName?: string;
|
||||||
|
authRequestId?: string;
|
||||||
isAlternative?: boolean; // whether password was requested as alternative auth method
|
isAlternative?: boolean; // whether password was requested as alternative auth method
|
||||||
promptPasswordless?: boolean;
|
promptPasswordless?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PasswordForm({
|
export default function PasswordForm({
|
||||||
loginName,
|
loginName,
|
||||||
|
authRequestId,
|
||||||
promptPasswordless,
|
promptPasswordless,
|
||||||
isAlternative,
|
isAlternative,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
@@ -44,6 +46,7 @@ export default function PasswordForm({
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName,
|
loginName,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
|
authRequestId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -73,7 +76,19 @@ export default function PasswordForm({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return router.push(`/accounts`);
|
return router.push(
|
||||||
|
`/signedin?` +
|
||||||
|
new URLSearchParams(
|
||||||
|
authRequestId
|
||||||
|
? {
|
||||||
|
loginName: resp.factors.user.loginName,
|
||||||
|
authRequestId,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
loginName: resp.factors.user.loginName,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export default function RegisterFormWithoutPassword({ legal }: Props) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName: loginName,
|
loginName: loginName,
|
||||||
|
// authRequestId, register does not need an oidc callback at the end
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import { XCircleIcon } from "@heroicons/react/24/outline";
|
|||||||
export default function SessionItem({
|
export default function SessionItem({
|
||||||
session,
|
session,
|
||||||
reload,
|
reload,
|
||||||
|
authRequestId,
|
||||||
}: {
|
}: {
|
||||||
session: Session;
|
session: Session;
|
||||||
reload: () => void;
|
reload: () => void;
|
||||||
|
authRequestId?: string;
|
||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -39,7 +41,7 @@ export default function SessionItem({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validPassword = session?.factors?.password?.verifiedAt;
|
const validPassword = session?.factors?.password?.verifiedAt;
|
||||||
const validPasskey = session?.factors?.passkey?.verifiedAt;
|
const validPasskey = session?.factors?.webAuthN?.verifiedAt;
|
||||||
|
|
||||||
const validUser = validPassword || validPasskey;
|
const validUser = validPassword || validPasskey;
|
||||||
|
|
||||||
@@ -48,14 +50,29 @@ export default function SessionItem({
|
|||||||
href={
|
href={
|
||||||
validUser
|
validUser
|
||||||
? `/signedin?` +
|
? `/signedin?` +
|
||||||
new URLSearchParams({
|
new URLSearchParams(
|
||||||
loginName: session.factors?.user?.loginName as string,
|
authRequestId
|
||||||
})
|
? {
|
||||||
|
loginName: session.factors?.user?.loginName as string,
|
||||||
|
authRequestId,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
loginName: session.factors?.user?.loginName as string,
|
||||||
|
}
|
||||||
|
)
|
||||||
: `/loginname?` +
|
: `/loginname?` +
|
||||||
new URLSearchParams({
|
new URLSearchParams(
|
||||||
loginName: session.factors?.user?.loginName as string,
|
authRequestId
|
||||||
submit: "true",
|
? {
|
||||||
})
|
loginName: session.factors?.user?.loginName as string,
|
||||||
|
submit: "true",
|
||||||
|
authRequestId,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
loginName: session.factors?.user?.loginName as string,
|
||||||
|
submit: "true",
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
className="group flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light hover:shadow-lg dark:hover:bg-white/10 py-2 px-4 rounded-md transition-all"
|
className="group flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light hover:shadow-lg dark:hover:bg-white/10 py-2 px-4 rounded-md transition-all"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import { useEffect, useState } from "react";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
sessions: Session[];
|
sessions: Session[];
|
||||||
|
authRequestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SessionsList({ sessions }: Props) {
|
export default function SessionsList({ sessions, authRequestId }: Props) {
|
||||||
const [list, setList] = useState<Session[]>(sessions);
|
const [list, setList] = useState<Session[]>(sessions);
|
||||||
return sessions ? (
|
return sessions ? (
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
@@ -19,6 +20,7 @@ export default function SessionsList({ sessions }: Props) {
|
|||||||
return (
|
return (
|
||||||
<SessionItem
|
<SessionItem
|
||||||
session={session}
|
session={session}
|
||||||
|
authRequestId={authRequestId}
|
||||||
reload={() => {
|
reload={() => {
|
||||||
setList(list.filter((s) => s.id !== session.id));
|
setList(list.filter((s) => s.id !== session.id));
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export default function SetPasswordForm({
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName: loginName,
|
loginName: loginName,
|
||||||
password: password,
|
password: password,
|
||||||
|
// authRequestId, register does not need an oidc callback
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export interface SignInWithIDPProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const START_IDP_FLOW_PATH = (idpId: string) =>
|
const START_IDP_FLOW_PATH = (idpId: string) =>
|
||||||
`/v2alpha/users/idps/${idpId}/start`;
|
`/v2beta/users/idps/${idpId}/start`;
|
||||||
|
|
||||||
export function SignInWithIDP({
|
export function SignInWithIDP({
|
||||||
host,
|
host,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useForm } from "react-hook-form";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Spinner } from "./Spinner";
|
import { Spinner } from "./Spinner";
|
||||||
import { LoginSettings } from "@zitadel/server";
|
import { LoginSettings } from "@zitadel/server";
|
||||||
|
import Alert from "./Alert";
|
||||||
|
|
||||||
type Inputs = {
|
type Inputs = {
|
||||||
loginName: string;
|
loginName: string;
|
||||||
@@ -15,12 +16,14 @@ type Inputs = {
|
|||||||
type Props = {
|
type Props = {
|
||||||
loginSettings: LoginSettings | undefined;
|
loginSettings: LoginSettings | undefined;
|
||||||
loginName: string | undefined;
|
loginName: string | undefined;
|
||||||
|
authRequestId: string | undefined;
|
||||||
submit: boolean;
|
submit: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function UsernameForm({
|
export default function UsernameForm({
|
||||||
loginSettings,
|
loginSettings,
|
||||||
loginName,
|
loginName,
|
||||||
|
authRequestId,
|
||||||
submit,
|
submit,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||||
@@ -37,19 +40,25 @@ export default function UsernameForm({
|
|||||||
|
|
||||||
async function submitLoginName(values: Inputs) {
|
async function submitLoginName(values: Inputs) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
loginName: values.loginName,
|
||||||
|
};
|
||||||
|
|
||||||
const res = await fetch("/api/loginname", {
|
const res = await fetch("/api/loginname", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(authRequestId ? { ...body, authRequestId } : body),
|
||||||
loginName: values.loginName,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error("Failed to load authentication methods");
|
const response = await res.json();
|
||||||
|
|
||||||
|
setError(response.details);
|
||||||
|
return Promise.reject(response.details);
|
||||||
}
|
}
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
@@ -60,33 +69,40 @@ export default function UsernameForm({
|
|||||||
const method = response.authMethodTypes[0];
|
const method = response.authMethodTypes[0];
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 1: //AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSWORD:
|
case 1: //AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSWORD:
|
||||||
|
const paramsPassword: any = { loginName: values.loginName };
|
||||||
|
|
||||||
|
if (loginSettings?.passkeysType === 1) {
|
||||||
|
paramsPassword.promptPasswordless = `true`; // PasskeysType.PASSKEYS_TYPE_ALLOWED,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
paramsPassword.authRequestId = authRequestId;
|
||||||
|
}
|
||||||
|
|
||||||
return router.push(
|
return router.push(
|
||||||
"/password?" +
|
"/password?" + new URLSearchParams(paramsPassword)
|
||||||
new URLSearchParams(
|
|
||||||
loginSettings?.passkeysType === 1
|
|
||||||
? {
|
|
||||||
loginName: values.loginName,
|
|
||||||
promptPasswordless: `true`, // PasskeysType.PASSKEYS_TYPE_ALLOWED,
|
|
||||||
}
|
|
||||||
: { loginName: values.loginName }
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
case 2: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
|
case 2: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
|
||||||
|
const paramsPasskey: any = { loginName: values.loginName };
|
||||||
|
if (authRequestId) {
|
||||||
|
paramsPasskey.authRequestId = authRequestId;
|
||||||
|
}
|
||||||
|
|
||||||
return router.push(
|
return router.push(
|
||||||
"/passkey/login?" +
|
"/passkey/login?" + new URLSearchParams(paramsPasskey)
|
||||||
new URLSearchParams({ loginName: values.loginName })
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
const paramsPasskeyDefault: any = { loginName: values.loginName };
|
||||||
|
|
||||||
|
if (loginSettings?.passkeysType === 1) {
|
||||||
|
paramsPasskeyDefault.promptPasswordless = `true`; // PasskeysType.PASSKEYS_TYPE_ALLOWED,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
paramsPasskeyDefault.authRequestId = authRequestId;
|
||||||
|
}
|
||||||
return router.push(
|
return router.push(
|
||||||
"/password?" +
|
"/password?" + new URLSearchParams(paramsPasskeyDefault)
|
||||||
new URLSearchParams(
|
|
||||||
loginSettings?.passkeysType === 1
|
|
||||||
? {
|
|
||||||
loginName: values.loginName,
|
|
||||||
promptPasswordless: `true`, // PasskeysType.PASSKEYS_TYPE_ALLOWED,
|
|
||||||
}
|
|
||||||
: { loginName: values.loginName }
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
@@ -99,12 +115,17 @@ export default function UsernameForm({
|
|||||||
} else {
|
} else {
|
||||||
// prefer passkey in favor of other methods
|
// prefer passkey in favor of other methods
|
||||||
if (response.authMethodTypes.includes(2)) {
|
if (response.authMethodTypes.includes(2)) {
|
||||||
|
const passkeyParams: any = {
|
||||||
|
loginName: values.loginName,
|
||||||
|
altPassword: `${response.authMethodTypes.includes(1)}`, // show alternative password option
|
||||||
|
};
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
passkeyParams.authRequestId = authRequestId;
|
||||||
|
}
|
||||||
|
|
||||||
return router.push(
|
return router.push(
|
||||||
"/passkey/login?" +
|
"/passkey/login?" + new URLSearchParams(passkeyParams)
|
||||||
new URLSearchParams({
|
|
||||||
loginName: values.loginName,
|
|
||||||
altPassword: `${response.authMethodTypes.includes(1)}`, // show alternative password option
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,14 +149,16 @@ export default function UsernameForm({
|
|||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
{...register("loginName", { required: "This field is required" })}
|
{...register("loginName", { required: "This field is required" })}
|
||||||
label="Loginname"
|
label="Loginname"
|
||||||
// error={errors.username?.message as string}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="py-4">
|
||||||
|
<Alert>{error}</Alert>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mt-8 flex w-full flex-row items-center">
|
<div className="mt-8 flex w-full flex-row items-center">
|
||||||
{/* <Button type="button" variant={ButtonVariants.Secondary}>
|
|
||||||
back
|
|
||||||
</Button> */}
|
|
||||||
<span className="flex-grow"></span>
|
<span className="flex-grow"></span>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type SessionCookie = {
|
|||||||
token: string;
|
token: string;
|
||||||
loginName: string;
|
loginName: string;
|
||||||
changeDate: string;
|
changeDate: string;
|
||||||
|
authRequestId?: string; // if its linked to an OIDC flow
|
||||||
};
|
};
|
||||||
|
|
||||||
function setSessionHttpOnlyCookie(sessions: SessionCookie[]) {
|
function setSessionHttpOnlyCookie(sessions: SessionCookie[]) {
|
||||||
@@ -134,7 +135,7 @@ export async function getSessionCookieByLoginName(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllSessionIds(): Promise<any> {
|
export async function getAllSessionCookieIds(): Promise<any> {
|
||||||
const cookiesList = cookies();
|
const cookiesList = cookies();
|
||||||
const stringifiedCookie = cookiesList.get("sessions");
|
const stringifiedCookie = cookiesList.get("sessions");
|
||||||
|
|
||||||
@@ -146,6 +147,18 @@ export async function getAllSessionIds(): Promise<any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAllSessions(): Promise<SessionCookie[]> {
|
||||||
|
const cookiesList = cookies();
|
||||||
|
const stringifiedCookie = cookiesList.get("sessions");
|
||||||
|
|
||||||
|
if (stringifiedCookie?.value) {
|
||||||
|
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
|
||||||
|
return sessions;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns most recent session filtered by optinal loginName
|
* Returns most recent session filtered by optinal loginName
|
||||||
* @param loginName
|
* @param loginName
|
||||||
|
|||||||
@@ -4,18 +4,17 @@ import {
|
|||||||
addSessionToCookie,
|
addSessionToCookie,
|
||||||
updateSessionCookie,
|
updateSessionCookie,
|
||||||
} from "./cookies";
|
} from "./cookies";
|
||||||
import { ChallengeKind, Session, Challenges } from "@zitadel/server";
|
import { Session, Challenges, RequestChallenges } from "@zitadel/server";
|
||||||
|
|
||||||
export async function createSessionAndUpdateCookie(
|
export async function createSessionAndUpdateCookie(
|
||||||
loginName: string,
|
loginName: string,
|
||||||
password: string | undefined,
|
password: string | undefined,
|
||||||
domain: string,
|
challenges: RequestChallenges | undefined,
|
||||||
challenges: ChallengeKind[] | undefined
|
authRequestId: string | undefined
|
||||||
): Promise<Session> {
|
): Promise<Session> {
|
||||||
const createdSession = await createSession(
|
const createdSession = await createSession(
|
||||||
server,
|
server,
|
||||||
loginName,
|
loginName,
|
||||||
domain,
|
|
||||||
password,
|
password,
|
||||||
challenges
|
challenges
|
||||||
);
|
);
|
||||||
@@ -34,6 +33,10 @@ export async function createSessionAndUpdateCookie(
|
|||||||
loginName: response.session?.factors?.user?.loginName ?? "",
|
loginName: response.session?.factors?.user?.loginName ?? "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
sessionCookie.authRequestId = authRequestId;
|
||||||
|
}
|
||||||
|
|
||||||
return addSessionToCookie(sessionCookie).then(() => {
|
return addSessionToCookie(sessionCookie).then(() => {
|
||||||
return response.session as Session;
|
return response.session as Session;
|
||||||
});
|
});
|
||||||
@@ -55,17 +58,16 @@ export async function setSessionAndUpdateCookie(
|
|||||||
sessionToken: string,
|
sessionToken: string,
|
||||||
loginName: string,
|
loginName: string,
|
||||||
password: string | undefined,
|
password: string | undefined,
|
||||||
passkey: { credentialAssertionData: any } | undefined,
|
webAuthN: { credentialAssertionData: any } | undefined,
|
||||||
domain: string | undefined,
|
challenges: RequestChallenges | undefined,
|
||||||
challenges: ChallengeKind[] | undefined
|
authRequestId: string | undefined
|
||||||
): Promise<SessionWithChallenges> {
|
): Promise<SessionWithChallenges> {
|
||||||
return setSession(
|
return setSession(
|
||||||
server,
|
server,
|
||||||
sessionId,
|
sessionId,
|
||||||
sessionToken,
|
sessionToken,
|
||||||
domain,
|
|
||||||
password,
|
password,
|
||||||
passkey,
|
webAuthN,
|
||||||
challenges
|
challenges
|
||||||
).then((updatedSession) => {
|
).then((updatedSession) => {
|
||||||
if (updatedSession) {
|
if (updatedSession) {
|
||||||
@@ -76,24 +78,40 @@ export async function setSessionAndUpdateCookie(
|
|||||||
loginName: loginName,
|
loginName: loginName,
|
||||||
};
|
};
|
||||||
|
|
||||||
return getSession(server, sessionCookie.id, sessionCookie.token).then(
|
if (authRequestId) {
|
||||||
(response) => {
|
sessionCookie.authRequestId = authRequestId;
|
||||||
if (response?.session && response.session.factors?.user?.loginName) {
|
}
|
||||||
const { session } = response;
|
|
||||||
const newCookie: SessionCookie = {
|
|
||||||
id: sessionCookie.id,
|
|
||||||
token: updatedSession.sessionToken,
|
|
||||||
changeDate: session.changeDate?.toString() ?? "",
|
|
||||||
loginName: session.factors?.user?.loginName ?? "",
|
|
||||||
};
|
|
||||||
|
|
||||||
return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
|
return new Promise((resolve) => setTimeout(resolve, 1000)).then(() =>
|
||||||
return { challenges: updatedSession.challenges, ...session };
|
// TODO: remove
|
||||||
});
|
getSession(server, sessionCookie.id, sessionCookie.token).then(
|
||||||
} else {
|
(response) => {
|
||||||
throw "could not get session or session does not have loginName";
|
if (
|
||||||
|
response?.session &&
|
||||||
|
response.session.factors?.user?.loginName
|
||||||
|
) {
|
||||||
|
const { session } = response;
|
||||||
|
const newCookie: SessionCookie = {
|
||||||
|
id: sessionCookie.id,
|
||||||
|
token: updatedSession.sessionToken,
|
||||||
|
changeDate: session.changeDate?.toString() ?? "",
|
||||||
|
loginName: session.factors?.user?.loginName ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sessionCookie.authRequestId) {
|
||||||
|
newCookie.authRequestId = sessionCookie.authRequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateSessionCookie(sessionCookie.id, newCookie).then(
|
||||||
|
() => {
|
||||||
|
return { challenges: updatedSession.challenges, ...session };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw "could not get session or session does not have loginName";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw "Session not be set";
|
throw "Session not be set";
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"test:unit:watch": "jest --watch",
|
"test:unit:watch": "jest --watch",
|
||||||
"dev": "tsup --watch --dts",
|
"dev": "tsup --watch --dts",
|
||||||
"lint": "eslint \"src/**/*.ts*\"",
|
"lint": "eslint \"src/**/*.ts*\"",
|
||||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist && rm -rf src/proto"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bufbuild/buf": "^1.14.0",
|
"@bufbuild/buf": "^1.14.0",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"test:unit:watch": "jest --watch",
|
"test:unit:watch": "jest --watch",
|
||||||
"dev": "tsup --dts --watch",
|
"dev": "tsup --dts --watch",
|
||||||
"lint": "eslint \"src/**/*.ts*\"",
|
"lint": "eslint \"src/**/*.ts*\"",
|
||||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist && rm -rf src/proto"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bufbuild/buf": "^1.14.0",
|
"@bufbuild/buf": "^1.14.0",
|
||||||
|
|||||||
@@ -1,44 +1,58 @@
|
|||||||
import * as settings from "./v2/settings";
|
import * as settings from "./v2/settings";
|
||||||
import * as session from "./v2/session";
|
import * as session from "./v2/session";
|
||||||
import * as user from "./v2/user";
|
import * as user from "./v2/user";
|
||||||
|
import * as oidc from "./v2/oidc";
|
||||||
import * as management from "./management";
|
import * as management from "./management";
|
||||||
|
|
||||||
import * as login from "./proto/server/zitadel/settings/v2alpha/login_settings";
|
import * as login from "./proto/server/zitadel/settings/v2beta/login_settings";
|
||||||
import * as password from "./proto/server/zitadel/settings/v2alpha/password_settings";
|
import * as password from "./proto/server/zitadel/settings/v2beta/password_settings";
|
||||||
import * as legal from "./proto/server/zitadel/settings/v2alpha/legal_settings";
|
import * as legal from "./proto/server/zitadel/settings/v2beta/legal_settings";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
BrandingSettings,
|
BrandingSettings,
|
||||||
Theme,
|
Theme,
|
||||||
} from "./proto/server/zitadel/settings/v2alpha/branding_settings";
|
} from "./proto/server/zitadel/settings/v2beta/branding_settings";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
LoginSettings,
|
LoginSettings,
|
||||||
IdentityProvider,
|
IdentityProvider,
|
||||||
IdentityProviderType,
|
IdentityProviderType,
|
||||||
} from "./proto/server/zitadel/settings/v2alpha/login_settings";
|
} from "./proto/server/zitadel/settings/v2beta/login_settings";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ChallengeKind,
|
RequestChallenges,
|
||||||
Challenges,
|
Challenges,
|
||||||
Challenges_Passkey,
|
Challenges_WebAuthN,
|
||||||
} from "./proto/server/zitadel/session/v2alpha/challenge";
|
} from "./proto/server/zitadel/session/v2beta/challenge";
|
||||||
|
|
||||||
|
export {
|
||||||
|
GetAuthRequestRequest,
|
||||||
|
GetAuthRequestResponse,
|
||||||
|
CreateCallbackRequest,
|
||||||
|
CreateCallbackResponse,
|
||||||
|
} from "./proto/server/zitadel/oidc/v2beta/oidc_service";
|
||||||
|
|
||||||
|
export {
|
||||||
|
AuthRequest,
|
||||||
|
Prompt,
|
||||||
|
} from "./proto/server/zitadel/oidc/v2beta/authorization";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Session,
|
Session,
|
||||||
Factors,
|
Factors,
|
||||||
} from "./proto/server/zitadel/session/v2alpha/session";
|
} from "./proto/server/zitadel/session/v2beta/session";
|
||||||
export {
|
export {
|
||||||
IDPInformation,
|
IDPInformation,
|
||||||
IDPLink,
|
IDPLink,
|
||||||
} from "./proto/server/zitadel/user/v2alpha/idp";
|
} from "./proto/server/zitadel/user/v2beta/idp";
|
||||||
export {
|
export {
|
||||||
ListSessionsResponse,
|
ListSessionsResponse,
|
||||||
GetSessionResponse,
|
GetSessionResponse,
|
||||||
CreateSessionResponse,
|
CreateSessionResponse,
|
||||||
SetSessionResponse,
|
SetSessionResponse,
|
||||||
|
SetSessionRequest,
|
||||||
DeleteSessionResponse,
|
DeleteSessionResponse,
|
||||||
} from "./proto/server/zitadel/session/v2alpha/session_service";
|
} from "./proto/server/zitadel/session/v2beta/session_service";
|
||||||
export {
|
export {
|
||||||
GetPasswordComplexitySettingsResponse,
|
GetPasswordComplexitySettingsResponse,
|
||||||
GetBrandingSettingsResponse,
|
GetBrandingSettingsResponse,
|
||||||
@@ -48,7 +62,7 @@ export {
|
|||||||
GetLoginSettingsRequest,
|
GetLoginSettingsRequest,
|
||||||
GetActiveIdentityProvidersResponse,
|
GetActiveIdentityProvidersResponse,
|
||||||
GetActiveIdentityProvidersRequest,
|
GetActiveIdentityProvidersRequest,
|
||||||
} from "./proto/server/zitadel/settings/v2alpha/settings_service";
|
} from "./proto/server/zitadel/settings/v2beta/settings_service";
|
||||||
export {
|
export {
|
||||||
AddHumanUserResponse,
|
AddHumanUserResponse,
|
||||||
AddHumanUserRequest,
|
AddHumanUserRequest,
|
||||||
@@ -62,19 +76,19 @@ export {
|
|||||||
ListAuthenticationMethodTypesResponse,
|
ListAuthenticationMethodTypesResponse,
|
||||||
ListAuthenticationMethodTypesRequest,
|
ListAuthenticationMethodTypesRequest,
|
||||||
AuthenticationMethodType,
|
AuthenticationMethodType,
|
||||||
StartIdentityProviderFlowRequest,
|
StartIdentityProviderIntentRequest,
|
||||||
StartIdentityProviderFlowResponse,
|
StartIdentityProviderIntentResponse,
|
||||||
RetrieveIdentityProviderInformationRequest,
|
RetrieveIdentityProviderIntentRequest,
|
||||||
RetrieveIdentityProviderInformationResponse,
|
RetrieveIdentityProviderIntentResponse,
|
||||||
} from "./proto/server/zitadel/user/v2alpha/user_service";
|
} from "./proto/server/zitadel/user/v2beta/user_service";
|
||||||
export {
|
export {
|
||||||
SetHumanPasswordResponse,
|
SetHumanPasswordResponse,
|
||||||
SetHumanPasswordRequest,
|
SetHumanPasswordRequest,
|
||||||
} from "./proto/server/zitadel/management";
|
} from "./proto/server/zitadel/management";
|
||||||
export * from "./proto/server/zitadel/idp";
|
export * from "./proto/server/zitadel/idp";
|
||||||
export { type LegalAndSupportSettings } from "./proto/server/zitadel/settings/v2alpha/legal_settings";
|
export { type LegalAndSupportSettings } from "./proto/server/zitadel/settings/v2beta/legal_settings";
|
||||||
export { type PasswordComplexitySettings } from "./proto/server/zitadel/settings/v2alpha/password_settings";
|
export { type PasswordComplexitySettings } from "./proto/server/zitadel/settings/v2beta/password_settings";
|
||||||
export { type ResourceOwnerType } from "./proto/server/zitadel/settings/v2alpha/settings";
|
export { type ResourceOwnerType } from "./proto/server/zitadel/settings/v2beta/settings";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getServers,
|
getServers,
|
||||||
@@ -96,4 +110,5 @@ export {
|
|||||||
login,
|
login,
|
||||||
password,
|
password,
|
||||||
legal,
|
legal,
|
||||||
|
oidc,
|
||||||
};
|
};
|
||||||
|
|||||||
2
packages/zitadel-server/src/v2/oidc/index.ts
Normal file
2
packages/zitadel-server/src/v2/oidc/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./oidc";
|
||||||
|
export * from "../../proto/server/zitadel/oidc/v2beta/oidc_service";
|
||||||
24
packages/zitadel-server/src/v2/oidc/oidc.ts
Normal file
24
packages/zitadel-server/src/v2/oidc/oidc.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions";
|
||||||
|
|
||||||
|
import { ZitadelServer, createClient, getServers } from "../../server";
|
||||||
|
import { OIDCServiceClient, OIDCServiceDefinition } from ".";
|
||||||
|
|
||||||
|
export const getOidc = (server?: string | ZitadelServer) => {
|
||||||
|
let config;
|
||||||
|
if (server && typeof server === "string") {
|
||||||
|
const apps = getServers();
|
||||||
|
config = apps.find((a) => a.name === server)?.config;
|
||||||
|
} else if (server && typeof server === "object") {
|
||||||
|
config = server.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
throw Error("No ZITADEL server found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return createClient<OIDCServiceClient>(
|
||||||
|
OIDCServiceDefinition as CompatServiceDefinition,
|
||||||
|
config.apiUrl,
|
||||||
|
config.token
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from "./session";
|
export * from "./session";
|
||||||
export * from "../../proto/server/zitadel/session/v2alpha/session";
|
export * from "../../proto/server/zitadel/session/v2beta/session";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions";
|
|||||||
import {
|
import {
|
||||||
SessionServiceClient,
|
SessionServiceClient,
|
||||||
SessionServiceDefinition,
|
SessionServiceDefinition,
|
||||||
} from "../../proto/server/zitadel/session/v2alpha/session_service";
|
} from "../../proto/server/zitadel/session/v2beta/session_service";
|
||||||
|
|
||||||
import { ZitadelServer, createClient, getServers } from "../../server";
|
import { ZitadelServer, createClient, getServers } from "../../server";
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from "./settings";
|
export * from "./settings";
|
||||||
export * from "../../proto/server/zitadel/settings/v2alpha/settings";
|
export * from "../../proto/server/zitadel/settings/v2beta/settings";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions";
|
|||||||
import {
|
import {
|
||||||
SettingsServiceClient,
|
SettingsServiceClient,
|
||||||
SettingsServiceDefinition,
|
SettingsServiceDefinition,
|
||||||
} from "../../proto/server/zitadel/settings/v2alpha/settings_service";
|
} from "../../proto/server/zitadel/settings/v2beta/settings_service";
|
||||||
|
|
||||||
import { ZitadelServer, createClient, getServers } from "../../server";
|
import { ZitadelServer, createClient, getServers } from "../../server";
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from "./user";
|
export * from "./user";
|
||||||
export * from "../../proto/server/zitadel/user/v2alpha/user";
|
export * from "../../proto/server/zitadel/user/v2beta/user";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions";
|
|||||||
import {
|
import {
|
||||||
UserServiceClient,
|
UserServiceClient,
|
||||||
UserServiceDefinition,
|
UserServiceDefinition,
|
||||||
} from "../../proto/server/zitadel/user/v2alpha/user_service";
|
} from "../../proto/server/zitadel/user/v2beta/user_service";
|
||||||
|
|
||||||
import { ZitadelServer, createClient, getServers } from "../../server";
|
import { ZitadelServer, createClient, getServers } from "../../server";
|
||||||
|
|
||||||
|
|||||||
569
pnpm-lock.yaml
generated
569
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user