mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 10:07:34 +00:00
device code flow
This commit is contained in:
@@ -190,7 +190,7 @@
|
|||||||
"device": {
|
"device": {
|
||||||
"usercode": {
|
"usercode": {
|
||||||
"title": "Device code",
|
"title": "Device code",
|
||||||
"description": "Enter the code provided in the verification email.",
|
"description": "Enter the code.",
|
||||||
"submit": "Continue"
|
"submit": "Continue"
|
||||||
},
|
},
|
||||||
"request": {
|
"request": {
|
||||||
|
@@ -5,9 +5,11 @@ import { UserAvatar } from "@/components/user-avatar";
|
|||||||
import { getMostRecentCookieWithLoginname } from "@/lib/cookies";
|
import { getMostRecentCookieWithLoginname } from "@/lib/cookies";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||||
import {
|
import {
|
||||||
|
authorizeOrDenyDeviceAuthorization,
|
||||||
createCallback,
|
createCallback,
|
||||||
createResponse,
|
createResponse,
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
|
getDeviceAuthorizationRequest,
|
||||||
getLoginSettings,
|
getLoginSettings,
|
||||||
getSession,
|
getSession,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
@@ -24,7 +26,6 @@ import { redirect } from "next/navigation";
|
|||||||
|
|
||||||
async function loadSession(
|
async function loadSession(
|
||||||
serviceUrl: string,
|
serviceUrl: string,
|
||||||
|
|
||||||
loginName: string,
|
loginName: string,
|
||||||
requestId?: string,
|
requestId?: string,
|
||||||
) {
|
) {
|
||||||
@@ -62,6 +63,26 @@ async function loadSession(
|
|||||||
}).then(({ url }) => {
|
}).then(({ url }) => {
|
||||||
return redirect(url);
|
return redirect(url);
|
||||||
});
|
});
|
||||||
|
} else if (requestId && requestId.startsWith("device_")) {
|
||||||
|
const userCode = requestId.replace("device_", "");
|
||||||
|
|
||||||
|
const { deviceAuthorizationRequest } = await getDeviceAuthorizationRequest({
|
||||||
|
serviceUrl,
|
||||||
|
userCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!deviceAuthorizationRequest) {
|
||||||
|
throw new Error("Device authorization request not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizeOrDenyDeviceAuthorization({
|
||||||
|
serviceUrl,
|
||||||
|
deviceAuthorizationId: deviceAuthorizationRequest?.id,
|
||||||
|
session: {
|
||||||
|
sessionId: recent.id,
|
||||||
|
sessionToken: recent.token,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSession({
|
return getSession({
|
||||||
|
@@ -127,6 +127,7 @@ export async function GET(request: NextRequest) {
|
|||||||
request,
|
request,
|
||||||
});
|
});
|
||||||
} else if (requestId.startsWith("device_")) {
|
} else if (requestId.startsWith("device_")) {
|
||||||
|
// this finishes the login process for Device Authorization
|
||||||
return loginWithDeviceAndSession({
|
return loginWithDeviceAndSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
deviceRequest: requestId.replace("device_", ""),
|
deviceRequest: requestId.replace("device_", ""),
|
||||||
@@ -509,7 +510,9 @@ export async function GET(request: NextRequest) {
|
|||||||
requestId: `saml_${samlRequest.id}`,
|
requestId: `saml_${samlRequest.id}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
// Device Authorization does not need to start here as it is handled on the /device endpoint
|
||||||
|
else {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "No authRequest nor samlRequest provided" },
|
{ error: "No authRequest nor samlRequest provided" },
|
||||||
{ status: 500 },
|
{ status: 500 },
|
||||||
|
@@ -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_${response.deviceAuthorizationRequest.id}`,
|
requestId: `device_${userCode}`,
|
||||||
user_code: value.userCode,
|
user_code: value.userCode,
|
||||||
}).toString(),
|
}).toString(),
|
||||||
);
|
);
|
||||||
|
@@ -5,6 +5,28 @@ type FinishFlowCommand =
|
|||||||
}
|
}
|
||||||
| { loginName: string };
|
| { loginName: string };
|
||||||
|
|
||||||
|
function goToSignedInPage(
|
||||||
|
props:
|
||||||
|
| { sessionId: string; organization?: string }
|
||||||
|
| { organization?: string; loginName: string },
|
||||||
|
) {
|
||||||
|
const params = new URLSearchParams({});
|
||||||
|
|
||||||
|
if ("loginName" in props && props.loginName) {
|
||||||
|
params.append("loginName", props.loginName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("sessionId" in props && props.sessionId) {
|
||||||
|
params.append("sessionId", props.sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.organization) {
|
||||||
|
params.append("organization", props.organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/signedin?` + params;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* for client: redirects user back to an OIDC or SAML application or to a success page when using requestId, check if a default redirect and redirect to it, or just redirect to a success page with the loginName
|
* for client: redirects user back to an OIDC or SAML application or to a success page when using requestId, check if a default redirect and redirect to it, or just redirect to a success page with the loginName
|
||||||
* @param command
|
* @param command
|
||||||
@@ -14,7 +36,25 @@ export async function getNextUrl(
|
|||||||
command: FinishFlowCommand & { organization?: string },
|
command: FinishFlowCommand & { organization?: string },
|
||||||
defaultRedirectUri?: string,
|
defaultRedirectUri?: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if ("sessionId" in command && "requestId" in command) {
|
// finish Device Authorization Flow
|
||||||
|
if (
|
||||||
|
"requestId" in command &&
|
||||||
|
command.requestId.startsWith("device_") &&
|
||||||
|
("loginName" in command || "sessionId" in command)
|
||||||
|
) {
|
||||||
|
return goToSignedInPage({
|
||||||
|
...command,
|
||||||
|
organization: command.organization,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish SAML or OIDC flow
|
||||||
|
if (
|
||||||
|
"sessionId" in command &&
|
||||||
|
"requestId" in command &&
|
||||||
|
(command.requestId.startsWith("saml_") ||
|
||||||
|
command.requestId.startsWith("oidc_"))
|
||||||
|
) {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
sessionId: command.sessionId,
|
sessionId: command.sessionId,
|
||||||
requestId: command.requestId,
|
requestId: command.requestId,
|
||||||
@@ -31,13 +71,5 @@ export async function getNextUrl(
|
|||||||
return defaultRedirectUri;
|
return defaultRedirectUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
return goToSignedInPage(command);
|
||||||
loginName: command.loginName,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (command.organization) {
|
|
||||||
params.append("organization", command.organization);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `/signedin?` + params;
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user