Skip to content

Commit

Permalink
Merge pull request #40 from zitadel/oidc
Browse files Browse the repository at this point in the history
feat: oidc proxy, handle authRequest, callback
  • Loading branch information
peintnermax authored Sep 29, 2023
2 parents b330367 + 0e7e3ed commit e042be2
Show file tree
Hide file tree
Showing 41 changed files with 959 additions and 457 deletions.
14 changes: 10 additions & 4 deletions apps/login/app/(login)/accounts/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Session } from "@zitadel/server";
import { listSessions, server } from "#/lib/zitadel";
import { getAllSessionIds } from "#/utils/cookies";
import { getAllSessionCookieIds } from "#/utils/cookies";
import { UserPlusIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import SessionsList from "#/ui/SessionsList";

async function loadSessions(): Promise<Session[]> {
const ids = await getAllSessionIds();
const ids = await getAllSessionCookieIds();

if (ids && ids.length) {
const response = await listSessions(
Expand All @@ -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();

return (
Expand All @@ -29,7 +35,7 @@ export default async function Page() {
<p className="ztdl-p mb-6 block">Use your ZITADEL Account</p>

<div className="flex flex-col w-full space-y-2">
<SessionsList sessions={sessions} />
<SessionsList sessions={sessions} authRequestId={authRequestId} />
<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="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
Expand Down
122 changes: 122 additions & 0 deletions apps/login/app/(login)/login/route.ts
Original file line number Diff line number Diff line change
@@ -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();
}
}
2 changes: 2 additions & 0 deletions apps/login/app/(login)/loginname/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default async function Page({
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const loginName = searchParams?.loginName;
const authRequestId = searchParams?.authRequestId;
const submit: boolean = searchParams?.submit === "true";

const loginSettings = await getLoginSettings(server);
Expand All @@ -19,6 +20,7 @@ export default async function Page({
<UsernameForm
loginSettings={loginSettings}
loginName={loginName}
authRequestId={authRequestId}
submit={submit}
/>
</div>
Expand Down
3 changes: 2 additions & 1 deletion apps/login/app/(login)/passkey/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default async function Page({
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const { loginName, altPassword } = searchParams;
const { loginName, altPassword, authRequestId } = searchParams;

const sessionFactors = await loadSession(loginName);

Expand Down Expand Up @@ -48,6 +48,7 @@ export default async function Page({
{loginName && (
<LoginPasskey
loginName={loginName}
authRequestId={authRequestId}
altPassword={altPassword === "true"}
/>
)}
Expand Down
3 changes: 2 additions & 1 deletion apps/login/app/(login)/password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default async function Page({
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const { loginName, promptPasswordless, alt } = searchParams;
const { loginName, promptPasswordless, authRequestId, alt } = searchParams;
const sessionFactors = await loadSession(loginName);

async function loadSession(loginName?: string) {
Expand Down Expand Up @@ -46,6 +46,7 @@ export default async function Page({

<PasswordForm
loginName={loginName}
authRequestId={authRequestId}
promptPasswordless={promptPasswordless === "true"}
isAlternative={alt === "true"}
/>
Expand Down
19 changes: 11 additions & 8 deletions apps/login/app/(login)/register/idp/[provider]/success/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ProviderSlug } from "#/lib/demos";
import { addHumanUser, server } from "#/lib/zitadel";
import { server } from "#/lib/zitadel";
import Alert, { AlertType } from "#/ui/Alert";
import {
AddHumanUserRequest,
IDPInformation,
RetrieveIdentityProviderInformationResponse,
RetrieveIdentityProviderIntentResponse,
user,
IDPLink,
} from "@zitadel/server";
Expand All @@ -27,8 +27,8 @@ const PROVIDER_MAPPING: {
// organisation: Organisation | undefined;
profile: {
displayName: idp.rawInformation?.User?.name ?? "",
firstName: idp.rawInformation?.User?.given_name ?? "",
lastName: idp.rawInformation?.User?.family_name ?? "",
givenName: idp.rawInformation?.User?.given_name ?? "",
familyName: idp.rawInformation?.User?.family_name ?? "",
},
idpLinks: [idpLink],
};
Expand All @@ -49,8 +49,8 @@ const PROVIDER_MAPPING: {
// organisation: Organisation | undefined;
profile: {
displayName: idp.rawInformation?.name ?? "",
firstName: idp.rawInformation?.name ?? "",
lastName: idp.rawInformation?.name ?? "",
givenName: idp.rawInformation?.name ?? "",
familyName: idp.rawInformation?.name ?? "",
},
idpLinks: [idpLink],
};
Expand All @@ -64,8 +64,11 @@ function retrieveIDP(
): Promise<IDPInformation | undefined> {
const userService = user.getUser(server);
return userService
.retrieveIdentityProviderInformation({ intentId: id, token: token }, {})
.then((resp: RetrieveIdentityProviderInformationResponse) => {
.retrieveIdentityProviderIntent(
{ idpIntentId: id, idpIntentToken: token },
{}
)
.then((resp: RetrieveIdentityProviderIntentResponse) => {
return resp.idpInformation;
});
}
Expand Down
17 changes: 13 additions & 4 deletions apps/login/app/(login)/signedin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { getSession, server } from "#/lib/zitadel";
import { createCallback, getSession, server } from "#/lib/zitadel";
import UserAvatar from "#/ui/UserAvatar";
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}`);

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) => {
if (response?.session) {
return response.session;
Expand All @@ -13,8 +22,8 @@ async function loadSession(loginName: string) {
}

export default async function Page({ searchParams }: { searchParams: any }) {
const { loginName } = searchParams;
const sessionFactors = await loadSession(loginName);
const { loginName, authRequestId } = searchParams;
const sessionFactors = await loadSession(loginName, authRequestId);

return (
<div className="flex flex-col items-center space-y-4">
Expand Down
8 changes: 7 additions & 1 deletion apps/login/app/api/idp/start/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ export async function POST(request: NextRequest) {
if (body) {
let { idpId, successUrl, failureUrl } = body;

return startIdentityProviderFlow(server, { idpId, successUrl, failureUrl })
return startIdentityProviderFlow(server, {
idpId,
urls: {
successUrl,
failureUrl,
},
})
.then((resp) => {
return NextResponse.json(resp);
})
Expand Down
60 changes: 10 additions & 50 deletions apps/login/app/api/loginname/route.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,18 @@
import {
getSession,
listAuthenticationMethodTypes,
server,
} from "#/lib/zitadel";
import { getSessionCookieById } from "#/utils/cookies";
import { listAuthenticationMethodTypes } from "#/lib/zitadel";
import { createSessionAndUpdateCookie } from "#/utils/session";
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) {
const body = await request.json();
if (body) {
const { loginName } = body;

const domain: string = request.nextUrl.hostname;
const { loginName, authRequestId } = body;

return createSessionAndUpdateCookie(loginName, undefined, domain, undefined)
return createSessionAndUpdateCookie(
loginName,
undefined,
undefined,
authRequestId
)
.then((session) => {
if (session.factors?.user?.id) {
return listAuthenticationMethodTypes(session.factors?.user?.id)
Expand All @@ -62,16 +27,11 @@ export async function POST(request: NextRequest) {
return NextResponse.json(error, { status: 500 });
});
} else {
throw "No user id found in session";
throw { details: "No user id found in session" };
}
})
.catch((error) => {
return NextResponse.json(
{
details: "could not add session to cookie",
},
{ status: 500 }
);
return NextResponse.json(error, { status: 500 });
});
} else {
return NextResponse.error();
Expand Down
Loading

1 comment on commit e042be2

@vercel
Copy link

@vercel vercel bot commented on e042be2 Sep 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

typescript-login – ./

typescript-login.vercel.app
typescript-login-zitadel.vercel.app
typescript-login-git-main-zitadel.vercel.app

Please sign in to comment.