+
{branding && (
-
+ <>
+
+
+ {appName &&
}
+ >
)}
diff --git a/apps/login/src/lib/client.ts b/apps/login/src/lib/client.ts
index 953d66e7ee..a59af90b77 100644
--- a/apps/login/src/lib/client.ts
+++ b/apps/login/src/lib/client.ts
@@ -5,6 +5,33 @@ type FinishFlowCommand =
}
| { loginName: string };
+function goToSignedInPage(
+ props:
+ | { sessionId: string; organization?: string; requestId?: string }
+ | { organization?: string; loginName: string; requestId?: 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);
+ }
+
+ // required to show conditional UI for device flow
+ if (props.requestId) {
+ params.append("requestId", props.requestId);
+ }
+
+ 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
* @param command
@@ -14,7 +41,25 @@ export async function getNextUrl(
command: FinishFlowCommand & { organization?: string },
defaultRedirectUri?: string,
): Promise
{
- 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({
sessionId: command.sessionId,
requestId: command.requestId,
@@ -31,13 +76,5 @@ export async function getNextUrl(
return defaultRedirectUri;
}
- const params = new URLSearchParams({
- loginName: command.loginName,
- });
-
- if (command.organization) {
- params.append("organization", command.organization);
- }
-
- return `/signedin?` + params;
+ return goToSignedInPage(command);
}
diff --git a/apps/login/src/lib/oidc.ts b/apps/login/src/lib/oidc.ts
index 7efab0b254..b692300dea 100644
--- a/apps/login/src/lib/oidc.ts
+++ b/apps/login/src/lib/oidc.ts
@@ -11,7 +11,7 @@ import { NextRequest, NextResponse } from "next/server";
import { constructUrl } from "./service-url";
import { isSessionValid } from "./session";
-type LoginWithOIDCandSession = {
+type LoginWithOIDCAndSession = {
serviceUrl: string;
authRequest: string;
sessionId: string;
@@ -19,14 +19,14 @@ type LoginWithOIDCandSession = {
sessionCookies: Cookie[];
request: NextRequest;
};
-export async function loginWithOIDCandSession({
+export async function loginWithOIDCAndSession({
serviceUrl,
authRequest,
sessionId,
sessions,
sessionCookies,
request,
-}: LoginWithOIDCandSession) {
+}: LoginWithOIDCAndSession) {
console.log(
`Login with session: ${sessionId} and authRequest: ${authRequest}`,
);
diff --git a/apps/login/src/lib/saml.ts b/apps/login/src/lib/saml.ts
index c2664599b5..e85084f022 100644
--- a/apps/login/src/lib/saml.ts
+++ b/apps/login/src/lib/saml.ts
@@ -8,7 +8,7 @@ import { NextRequest, NextResponse } from "next/server";
import { constructUrl } from "./service-url";
import { isSessionValid } from "./session";
-type LoginWithSAMLandSession = {
+type LoginWithSAMLAndSession = {
serviceUrl: string;
samlRequest: string;
sessionId: string;
@@ -17,14 +17,14 @@ type LoginWithSAMLandSession = {
request: NextRequest;
};
-export async function loginWithSAMLandSession({
+export async function loginWithSAMLAndSession({
serviceUrl,
samlRequest,
sessionId,
sessions,
sessionCookies,
request,
-}: LoginWithSAMLandSession) {
+}: LoginWithSAMLAndSession) {
console.log(
`Login with session: ${sessionId} and samlRequest: ${samlRequest}`,
);
diff --git a/apps/login/src/lib/server/device.ts b/apps/login/src/lib/server/device.ts
new file mode 100644
index 0000000000..d96059f6a6
--- /dev/null
+++ b/apps/login/src/lib/server/device.ts
@@ -0,0 +1,20 @@
+"use server";
+
+import { authorizeOrDenyDeviceAuthorization } from "@/lib/zitadel";
+import { headers } from "next/headers";
+import { getServiceUrlFromHeaders } from "../service";
+
+export async function completeDeviceAuthorization(
+ deviceAuthorizationId: string,
+ session?: { sessionId: string; sessionToken: string },
+) {
+ const _headers = await headers();
+ const { serviceUrl } = getServiceUrlFromHeaders(_headers);
+
+ // without the session, device auth request is denied
+ return authorizeOrDenyDeviceAuthorization({
+ serviceUrl,
+ deviceAuthorizationId,
+ session,
+ });
+}
diff --git a/apps/login/src/lib/server/oidc.ts b/apps/login/src/lib/server/oidc.ts
new file mode 100644
index 0000000000..4ae01b4a47
--- /dev/null
+++ b/apps/login/src/lib/server/oidc.ts
@@ -0,0 +1,15 @@
+"use server";
+
+import { getDeviceAuthorizationRequest as zitadelGetDeviceAuthorizationRequest } from "@/lib/zitadel";
+import { headers } from "next/headers";
+import { getServiceUrlFromHeaders } from "../service";
+
+export async function getDeviceAuthorizationRequest(userCode: string) {
+ const _headers = await headers();
+ const { serviceUrl } = getServiceUrlFromHeaders(_headers);
+
+ return zitadelGetDeviceAuthorizationRequest({
+ serviceUrl,
+ userCode,
+ });
+}
diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts
index a5abc0dcb1..508baf1667 100644
--- a/apps/login/src/lib/zitadel.ts
+++ b/apps/login/src/lib/zitadel.ts
@@ -944,6 +944,45 @@ export async function getAuthRequest({
});
}
+export async function getDeviceAuthorizationRequest({
+ serviceUrl,
+ userCode,
+}: {
+ serviceUrl: string;
+ userCode: string;
+}) {
+ const oidcService = await createServiceForHost(OIDCService, serviceUrl);
+
+ return oidcService.getDeviceAuthorizationRequest({
+ userCode,
+ });
+}
+
+export async function authorizeOrDenyDeviceAuthorization({
+ serviceUrl,
+ deviceAuthorizationId,
+ session,
+}: {
+ serviceUrl: string;
+ deviceAuthorizationId: string;
+ session?: { sessionId: string; sessionToken: string };
+}) {
+ const oidcService = await createServiceForHost(OIDCService, serviceUrl);
+
+ return oidcService.authorizeOrDenyDeviceAuthorization({
+ deviceAuthorizationId,
+ decision: session
+ ? {
+ case: "session",
+ value: session,
+ }
+ : {
+ case: "deny",
+ value: {},
+ },
+ });
+}
+
export async function createCallback({
serviceUrl,
req,