diff --git a/app/admin/dashboard/clients/new/page.tsx b/app/admin/dashboard/clients/new/page.tsx index 6de6b0f..604c6d0 100644 --- a/app/admin/dashboard/clients/new/page.tsx +++ b/app/admin/dashboard/clients/new/page.tsx @@ -1,5 +1,13 @@ -import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, +} from "@/components/ui/card"; import { ClientForm } from "@/components/admin/client-form"; +import { OAuthEndpointsCard } from "@/components/admin/oauth-endpoints-card"; +import { getOpenIDConfiguration } from "@/lib/oauth/discovery"; export const metadata = { title: "New OAuth Client - Admin", @@ -7,17 +15,30 @@ export const metadata = { }; export default function NewClientPage() { + const config = getOpenIDConfiguration(); + + const endpoints = { + issuer: config.issuer, + authorizationEndpoint: config.authorization_endpoint, + tokenEndpoint: config.token_endpoint, + discoveryUrl: `${config.issuer}/.well-known/openid-configuration`, + }; + return ( - - - Create OAuth Client - - Register a new application that can use OAuth to authenticate users - - - - - - +
+ + + + + Create OAuth Client + + Register a new application that can use OAuth to authenticate users + + + + + + +
); } diff --git a/components/admin/oauth-endpoints-card.tsx b/components/admin/oauth-endpoints-card.tsx new file mode 100644 index 0000000..f546546 --- /dev/null +++ b/components/admin/oauth-endpoints-card.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { useState } from "react"; +import { Copy, Check } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, +} from "@/components/ui/card"; + +interface OAuthEndpointsCardProps { + endpoints: { + issuer: string; + authorizationEndpoint: string; + tokenEndpoint: string; + discoveryUrl: string; + }; +} + +export function OAuthEndpointsCard({ endpoints }: OAuthEndpointsCardProps) { + const [copied, setCopied] = useState(null); + + const handleCopy = (text: string, key: string) => { + navigator.clipboard.writeText(text); + setCopied(key); + setTimeout(() => setCopied(null), 2000); + }; + + const urlFields = [ + { key: "issuer", label: "Issuer URL", value: endpoints.issuer }, + { + key: "authorization", + label: "Authorization Endpoint", + value: endpoints.authorizationEndpoint, + }, + { key: "token", label: "Token Endpoint", value: endpoints.tokenEndpoint }, + { key: "discovery", label: "Discovery URL", value: endpoints.discoveryUrl }, + ]; + + return ( + + + OAuth Server Endpoints + + Use these URLs to configure your OAuth client application + + + + {urlFields.map(({ key, label, value }) => ( +
+ +
+ + +
+
+ ))} +

+ For automatic configuration, most OAuth libraries can use the + Discovery URL. +

+
+
+ ); +} diff --git a/e2e/admin/clients.spec.ts b/e2e/admin/clients.spec.ts index 53c61bd..b0b6956 100644 --- a/e2e/admin/clients.spec.ts +++ b/e2e/admin/clients.spec.ts @@ -9,11 +9,42 @@ async function adminLogin(page: import("@playwright/test").Page) { await expect(page).toHaveURL("/admin/dashboard"); } -test.describe("Client Form Scope Selection", () => { +test.describe("OAuth Endpoints Display", () => { test.beforeEach(async ({ page }) => { await adminLogin(page); }); + test("should display OAuth endpoints on new client page", async ({ page }) => { + await page.goto("/admin/dashboard/clients/new"); + + // Verify OAuth endpoints card is visible + await expect(page.getByText("OAuth Server Endpoints")).toBeVisible(); + + // Verify key endpoints are displayed + await expect(page.getByTestId("oauth-endpoint-issuer")).toBeVisible(); + await expect(page.getByTestId("oauth-endpoint-authorization")).toBeVisible(); + await expect(page.getByTestId("oauth-endpoint-token")).toBeVisible(); + await expect(page.getByTestId("oauth-endpoint-discovery")).toBeVisible(); + + // Verify endpoints have values + const issuerValue = await page.getByTestId("oauth-endpoint-issuer").inputValue(); + expect(issuerValue).toBeTruthy(); + + const authValue = await page.getByTestId("oauth-endpoint-authorization").inputValue(); + expect(authValue).toContain("/api/oauth/authorize"); + + const tokenValue = await page.getByTestId("oauth-endpoint-token").inputValue(); + expect(tokenValue).toContain("/api/oauth/token"); + + const discoveryValue = await page.getByTestId("oauth-endpoint-discovery").inputValue(); + expect(discoveryValue).toContain("/.well-known/openid-configuration"); + }); +}); + +test.describe("Client Form Scope Selection", () => { + test.beforeEach(async ({ page }) => { + await adminLogin(page); + }); test("should create client with all scopes selected", async ({ page }) => { await page.goto("/admin/dashboard/clients/new"); diff --git a/e2e/oauth/consent.spec.ts b/e2e/oauth/consent.spec.ts index c53e24f..9aa69f2 100644 --- a/e2e/oauth/consent.spec.ts +++ b/e2e/oauth/consent.spec.ts @@ -48,8 +48,8 @@ test.describe("OAuth Consent Flow", () => { await page.getByRole("button", { name: "Create Application" }).click(); await expect(page.getByText("Client Created Successfully")).toBeVisible(); - clientId = await page.locator('input[readonly]').first().inputValue(); - clientSecret = await page.locator('input[readonly]').nth(1).inputValue(); + clientId = await page.getByTestId("client-id-display").inputValue(); + clientSecret = await page.getByTestId("client-secret-display").inputValue(); await page.close(); await context.close();