Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

login prompt, callback handler #58

Merged
merged 29 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fb09285
rm forwarded header
peintnermax Mar 5, 2024
38e5f56
add x-zitadel-forwarded
peintnermax Mar 6, 2024
e7fc080
headers
peintnermax Mar 6, 2024
e366429
key
peintnermax Mar 6, 2024
f809cd3
auto return lastsession if no hint
peintnermax Mar 6, 2024
f6d8eff
debug view
peintnermax Mar 6, 2024
503783f
list env
peintnermax Mar 6, 2024
9e0d9d6
session, login prompt, select account to callback
peintnermax Mar 6, 2024
6d6372d
handle account select
peintnermax Mar 6, 2024
73fde48
submit sessionId
peintnermax Mar 6, 2024
482990c
login handler
peintnermax Mar 6, 2024
5f980f0
authRequestId as param
peintnermax Mar 6, 2024
a07bad5
authrequest searchparam
peintnermax Mar 6, 2024
7d579ec
authRequest context from accounts page
peintnermax Mar 6, 2024
251f275
request callback after pwd, passkey
peintnermax Mar 6, 2024
324cd89
security headers - csp
peintnermax Mar 8, 2024
4fcbf77
create prompt
peintnermax Mar 8, 2024
08fae94
login prompt
peintnermax Mar 8, 2024
ff404c7
hide nav
peintnermax Mar 8, 2024
88f4ac2
login hint for login prompt
peintnermax Mar 8, 2024
2ef1af8
password for multiple authmethods
peintnermax Mar 11, 2024
49b86d2
parse orgId from authrequest scope
peintnermax Mar 11, 2024
7668113
catch org context
peintnermax Mar 11, 2024
86e70ac
org filter
peintnermax Mar 12, 2024
aa7a404
Update apps/login/app/(login)/layout.tsx
peintnermax Mar 14, 2024
5f5f6f3
Update apps/login/app/(login)/login/route.ts
peintnermax Mar 14, 2024
0e9928d
Update apps/login/app/layout.tsx
peintnermax Mar 14, 2024
d653763
get group from string
peintnermax Mar 14, 2024
952a1c8
doc, 400
peintnermax Mar 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion apps/login/app/(login)/accounts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,16 @@ export default async function Page({

<div className="flex flex-col w-full space-y-2">
<SessionsList sessions={sessions} authRequestId={authRequestId} />
<Link href="/loginname">
<Link
href={
authRequestId
? `/loginname?` +
new URLSearchParams({
authRequestId,
})
: "/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">
<UserPlusIcon className="h-5 w-5" />
Expand Down
3 changes: 3 additions & 0 deletions apps/login/app/(login)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { Logo } from "#/ui/Logo";

export default async function Layout({
children,
params,
}: {
children: React.ReactNode;
params: any;
}) {
console.log(params);
peintnermax marked this conversation as resolved.
Show resolved Hide resolved
const branding = await getBrandingSettings(server);
let partial: Partial<BrandingSettings> | undefined;
if (branding) {
Expand Down
168 changes: 136 additions & 32 deletions apps/login/app/(login)/login/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
server,
} from "#/lib/zitadel";
import { SessionCookie, getAllSessions } from "#/utils/cookies";
import { Session, AuthRequest, Prompt } from "@zitadel/server";
import { Session, AuthRequest, Prompt, login } from "@zitadel/server";
import { NextRequest, NextResponse } from "next/server";

async function loadSessions(ids: string[]): Promise<Session[]> {
Expand All @@ -16,6 +16,8 @@ async function loadSessions(ids: string[]): Promise<Session[]> {
return response?.sessions ?? [];
}

const ORG_SCOPE_REGEX = /urn:zitadel:iam:org:id:([0-9]*)/g;
peintnermax marked this conversation as resolved.
Show resolved Hide resolved

function findSession(
sessions: Session[],
authRequest: AuthRequest
Expand All @@ -30,47 +32,144 @@ function findSession(
(s) => s.factors?.user?.loginName === authRequest.loginHint
);
}
if (sessions.length) {
return sessions[0];
}
return undefined;
}

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const authRequestId = searchParams.get("authRequest");
const sessionId = searchParams.get("sessionId");

const sessionCookies: SessionCookie[] = await getAllSessions();
const ids = sessionCookies.map((s) => s.id);
let sessions: Session[] = [];
if (ids && ids.length) {
sessions = await loadSessions(ids);
}

if (authRequestId && sessionId) {
console.log(
`Login with session: ${sessionId} and authRequest: ${authRequestId}`
);
peintnermax marked this conversation as resolved.
Show resolved Hide resolved

let selectedSession = sessions.find((s) => s.id === sessionId);

if (selectedSession && selectedSession.id) {
console.log(`Found session ${selectedSession.id}`);
const cookie = sessionCookies.find(
(cookie) => cookie.id === selectedSession?.id
);

if (cookie && cookie.id && cookie.token) {
console.log(`Found sessioncookie ${cookie.id}`);

const session = {
sessionId: cookie?.id,
sessionToken: cookie?.token,
};

const { callbackUrl } = await createCallback(server, {
authRequestId,
session,
});
return NextResponse.redirect(callbackUrl);
}
}
}

if (authRequestId) {
console.log(`Login with authRequest: ${authRequestId}`);
const { authRequest } = await getAuthRequest(server, { authRequestId });
const sessionCookies: SessionCookie[] = await getAllSessions();
const ids = sessionCookies.map((s) => s.id);
let organization;

let sessions: Session[] = [];
if (ids && ids.length) {
sessions = await loadSessions(ids);
} else {
console.info("No session cookie found.");
sessions = [];
if (
authRequest?.scope &&
authRequest.scope.find((s) => ORG_SCOPE_REGEX.test(s))
) {
const orgId = authRequest.scope.find((s) => ORG_SCOPE_REGEX.test(s));

if (orgId) {
const matched = orgId.replace("urn:zitadel:iam:org:id:", "");
peintnermax marked this conversation as resolved.
Show resolved Hide resolved
organization = matched;
}
}

if (authRequest && authRequest.prompt.includes(Prompt.PROMPT_CREATE)) {
const registerUrl = new URL("/register", request.url);
if (authRequest?.id) {
registerUrl.searchParams.set("authRequestId", authRequest?.id);
}
if (organization) {
registerUrl.searchParams.set("organization", organization);
}

return NextResponse.redirect(registerUrl);
}

// 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)
) {
if (authRequest.prompt.includes(Prompt.PROMPT_SELECT_ACCOUNT)) {
const accountsUrl = new URL("/accounts", request.url);
if (authRequest?.id) {
accountsUrl.searchParams.set("authRequestId", authRequest?.id);
}
if (organization) {
accountsUrl.searchParams.set("organization", organization);
}

return NextResponse.redirect(accountsUrl);
} else if (authRequest.prompt.includes(Prompt.PROMPT_LOGIN)) {
// if prompt is login
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);
}
if (organization) {
loginNameUrl.searchParams.set("organization", organization);
}
return NextResponse.redirect(loginNameUrl);
} else if (authRequest.prompt.includes(Prompt.PROMPT_NONE)) {
// NONE prompt - silent authentication

let selectedSession = findSession(sessions, authRequest);

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 {
return NextResponse.json(
{ error: "No active session found" },
{ status: 500 } // TODO: check for correct status code
peintnermax marked this conversation as resolved.
Show resolved Hide resolved
);
}
} else {
return NextResponse.json(
{ error: "No active session found" },
{ status: 500 } // TODO: check for correct status code
);
}
} 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
Expand All @@ -88,35 +187,40 @@ export async function GET(request: NextRequest) {
return NextResponse.redirect(callbackUrl);
} else {
const accountsUrl = new URL("/accounts", request.url);
if (authRequest?.id) {
accountsUrl.searchParams.set("authRequestId", authRequest?.id);
accountsUrl.searchParams.set("authRequestId", authRequestId);
if (organization) {
accountsUrl.searchParams.set("organization", organization);
}

return NextResponse.redirect(accountsUrl);
}
} else {
const accountsUrl = new URL("/accounts", request.url);
if (authRequest?.id) {
accountsUrl.searchParams.set("authRequestId", authRequest?.id);
accountsUrl.searchParams.set("authRequestId", authRequestId);
if (organization) {
accountsUrl.searchParams.set("organization", organization);
}

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
}

loginNameUrl.searchParams.set("authRequestId", authRequestId);
if (authRequest?.loginHint) {
loginNameUrl.searchParams.set("loginName", authRequest.loginHint);
loginNameUrl.searchParams.set("submit", "true"); // autosubmit
}

if (organization) {
loginNameUrl.searchParams.set("organization", organization);
}

return NextResponse.redirect(loginNameUrl);
}
} else {
return NextResponse.error();
return NextResponse.json(
{ error: "No authRequestId provided" },
{ status: 500 }
);
}
}
4 changes: 3 additions & 1 deletion apps/login/app/(login)/loginname/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ export default async function Page({
}) {
const loginName = searchParams?.loginName;
const authRequestId = searchParams?.authRequestId;
const organization = searchParams?.organization;
const submit: boolean = searchParams?.submit === "true";

const loginSettings = await getLoginSettings(server);
const loginSettings = await getLoginSettings(server, organization);

return (
<div className="flex flex-col items-center space-y-4">
Expand All @@ -21,6 +22,7 @@ export default async function Page({
loginSettings={loginSettings}
loginName={loginName}
authRequestId={authRequestId}
organization={organization}
submit={submit}
/>
</div>
Expand Down
10 changes: 7 additions & 3 deletions apps/login/app/api/loginname/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { listAuthenticationMethodTypes } from "#/lib/zitadel";
import { listAuthenticationMethodTypes, listUsers } from "#/lib/zitadel";
import { createSessionAndUpdateCookie } from "#/utils/session";
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
const body = await request.json();
if (body) {
const { loginName, authRequestId } = body;

const { loginName, authRequestId, organization } = body;
// TODO - search for users with org
// return listUsers(loginName).then((users) => {
// if (users.details && users.details.totalResult == 1) {
// }
return createSessionAndUpdateCookie(
loginName,
undefined,
Expand All @@ -33,6 +36,7 @@ export async function POST(request: NextRequest) {
.catch((error) => {
return NextResponse.json(error, { status: 500 });
});
// });
} else {
return NextResponse.error();
}
Expand Down
27 changes: 22 additions & 5 deletions apps/login/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getBrandingSettings } from "#/lib/zitadel";
import { server } from "../lib/zitadel";
import { BrandingSettings } from "@zitadel/server";
import ThemeProvider from "#/ui/ThemeProvider";
import Theme from "#/ui/Theme";

const lato = Lato({
weight: ["400", "700", "900"],
Expand All @@ -23,7 +24,7 @@ export default async function RootLayout({
children: React.ReactNode;
}) {
// later only shown with dev mode enabled
const showNav = true;
const showNav = process.env.DEBUG === "true" ? true : false;
peintnermax marked this conversation as resolved.
Show resolved Hide resolved

const branding = await getBrandingSettings(server);
let partial: Partial<BrandingSettings> | undefined;
Expand All @@ -44,11 +45,27 @@ export default async function RootLayout({
<ThemeWrapper branding={partial}>
<ThemeProvider>
<LayoutProviders>
<div className="h-screen overflow-y-scroll bg-background-light-600 dark:bg-background-dark-600 bg-[url('/grid-light.svg')] dark:bg-[url('/grid-dark.svg')]">
{showNav && <GlobalNav />}
<div
className={`h-screen overflow-y-scroll bg-background-light-600 dark:bg-background-dark-600 ${
showNav
? "bg-[url('/grid-light.svg')] dark:bg-[url('/grid-dark.svg')]"
: ""
}`}
>
{showNav ? (
<GlobalNav />
) : (
<div className="absolute bottom-0 right-0 flex flex-row p-4">
<Theme />
</div>
)}

<div className={`${showNav ? "lg:pl-72" : ""} pb-4`}>
<div className="mx-auto max-w-[440px] space-y-8 pt-20 lg:py-8">
<div
className={`${
showNav ? "lg:pl-72" : ""
} pb-4 flex flex-col justify-center h-full`}
>
<div className="mx-auto max-w-[440px] space-y-8 pt-20 lg:py-8 w-full">
{showNav && (
<div className="rounded-lg bg-vc-border-gradient dark:bg-dark-vc-border-gradient p-px shadow-lg shadow-black/5 dark:shadow-black/20">
<div className="rounded-lg bg-background-light-400 dark:bg-background-dark-500">
Expand Down
Loading
Loading