This commit is contained in:
Max Peintner
2025-05-02 17:20:28 +02:00
parent 6270cf9522
commit 54fd748b12
5 changed files with 90 additions and 32 deletions

View File

@@ -197,6 +197,11 @@
"title": "would like to connect:", "title": "would like to connect:",
"description": "By clicking Allow, you allow this app and Zitadel to use your information in accordance with their respective terms of service and privacy policies. You can revoke this access at any time.", "description": "By clicking Allow, you allow this app and Zitadel to use your information in accordance with their respective terms of service and privacy policies. You can revoke this access at any time.",
"submit": "Allow" "submit": "Allow"
},
"scope": {
"email": "Access your email address.",
"profile": "Access your full profile information.",
"offline_access": "Allow offline access to your account."
} }
}, },
"error": { "error": {

View File

@@ -33,6 +33,8 @@ export default async function Page(props: {
userCode, userCode,
}); });
console.log(deviceAuthorizationRequest);
let defaultOrganization; let defaultOrganization;
if (!organization) { if (!organization) {
const org: Organization | null = await getDefaultOrg({ const org: Organization | null = await getDefaultOrg({
@@ -48,19 +50,28 @@ export default async function Page(props: {
organization: organization ?? defaultOrganization, organization: organization ?? defaultOrganization,
}); });
const params = new URLSearchParams();
if (requestId) {
params.append("requestId", requestId);
}
if (organization) {
params.append("organization", organization);
}
return ( return (
<DynamicTheme <DynamicTheme
branding={branding} branding={branding}
appName={deviceAuthorizationRequest?.appName} appName={deviceAuthorizationRequest?.appName}
> >
<div className="flex flex-col items-center space-y-4"> <div className="flex flex-col items-center space-y-4">
{!userCode && (
<>
<h1>{t("usercode.title")}</h1> <h1>{t("usercode.title")}</h1>
<p className="ztdl-p">{t("usercode.description")}</p> <p className="ztdl-p">{t("usercode.description")}</p>
<ConsentScreen scope={deviceAuthorizationRequest?.scope} /> <ConsentScreen
</> scope={deviceAuthorizationRequest?.scope}
)} nextUrl={`/loginname?` + params}
/>
</div> </div>
</DynamicTheme> </DynamicTheme>
); );

View File

@@ -9,7 +9,6 @@ import {
createCallback, createCallback,
createResponse, createResponse,
getBrandingSettings, getBrandingSettings,
getDeviceAuthorizationRequest,
getLoginSettings, getLoginSettings,
getSession, getSession,
} from "@/lib/zitadel"; } from "@/lib/zitadel";
@@ -64,24 +63,17 @@ async function loadSession(
return redirect(url); return redirect(url);
}); });
} else if (requestId && requestId.startsWith("device_")) { } else if (requestId && requestId.startsWith("device_")) {
const userCode = requestId.replace("device_", ""); const session = {
sessionId: recent.id,
const { deviceAuthorizationRequest } = await getDeviceAuthorizationRequest({ sessionToken: recent.token,
serviceUrl, };
userCode,
});
if (!deviceAuthorizationRequest) {
throw new Error("Device authorization request not found");
}
return authorizeOrDenyDeviceAuthorization({ return authorizeOrDenyDeviceAuthorization({
serviceUrl, serviceUrl,
deviceAuthorizationId: deviceAuthorizationRequest?.id, deviceAuthorizationId: requestId.replace("device_", ""),
session: { session,
sessionId: recent.id, }).then(() => {
sessionToken: recent.token, return session;
},
}); });
} }
@@ -105,7 +97,11 @@ export default async function Page(props: { searchParams: Promise<any> }) {
const { serviceUrl } = getServiceUrlFromHeaders(_headers); const { serviceUrl } = getServiceUrlFromHeaders(_headers);
const { loginName, requestId, organization } = searchParams; const { loginName, requestId, organization } = searchParams;
const sessionFactors = await loadSession(serviceUrl, loginName, requestId); // const sessionFactors = await loadSession(serviceUrl, loginName, requestId);
const sessionFactors = sessionId
? await loadSessionById(serviceUrl, sessionId, organization)
: await loadSessionByLoginname(serviceUrl, loginName, organization);
const branding = await getBrandingSettings({ const branding = await getBrandingSettings({
serviceUrl, serviceUrl,

View File

@@ -1,11 +1,57 @@
export function ConsentScreen({ scope }: { scope?: string[] }) { import { useTranslations } from "next-intl";
import Link from "next/link";
import { Button, ButtonVariants } from "./button";
export function ConsentScreen({
scope,
nextUrl,
}: {
scope?: string[];
nextUrl: string;
}) {
const t = useTranslations();
return ( return (
<div className="flex flex-col items-center space-y-4"> <div className="w-full flex flex-col items-center space-y-4">
<h1>Consent</h1> <ul className="list-disc space-y-2 w-full">
<p className="ztdl-p">Please confirm your consent.</p> {scope?.map((s) => {
<div className="flex flex-col items-center space-y-4"> const translationKey = `device.scope.${s}`;
<button className="btn btn-primary">Accept</button> const description = t(translationKey, null);
<button className="btn btn-secondary">Reject</button>
// Check if the key itself is returned and provide a fallback
const resolvedDescription =
description === translationKey
? "No description available."
: description;
return (
<li
key={s}
className="grid grid-cols-4 w-full text-sm flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light py-2 px-4 rounded-md transition-all"
>
<strong className="col-span-1">{s}</strong>
<span className="col-span-3">{resolvedDescription}</span>
</li>
);
})}
</ul>
<div className="mt-4 flex w-full flex-row items-center">
<Button variant={ButtonVariants.Destructive} data-testid="deny-button">
Deny
</Button>
<span className="flex-grow"></span>
<Link href={nextUrl}>
<Button
data-testid="submit-button"
type="submit"
className="self-end"
variant={ButtonVariants.Primary}
>
continue
</Button>
</Link>
</div> </div>
</div> </div>
); );

View File

@@ -51,7 +51,7 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) {
return router.push( return router.push(
`/device/consent?` + `/device/consent?` +
new URLSearchParams({ new URLSearchParams({
requestId: `device_${userCode}`, requestId: `device_${response.deviceAuthorizationRequest.id}`,
user_code: value.userCode, user_code: value.userCode,
}).toString(), }).toString(),
); );