From 56417f89fdbb37d2ed56fddb54ae1ee533527d0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:44:41 +0000 Subject: [PATCH 1/2] Initial plan From dd9a261c36fdc85e38f6f1bcda30dec74c3d585c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:54:05 +0000 Subject: [PATCH 2/2] Skip account selection for first-time sign-in during OAuth flow Add fresh_login parameter to skip account selection when user just logged in as part of the OAuth flow. Show account selection only when user was already signed in before starting the OAuth flow. - Modified authorize route to add fresh_login=true when redirecting to login - Modified authorize route to skip account selection when fresh_login=true - Modified switchAccount to add fresh_login=true after switching accounts - Added E2E tests for both scenarios Co-authored-by: sirily11 <32106111+sirily11@users.noreply.github.com> --- actions/oauth/account-select.ts | 1 + app/api/oauth/authorize/route.ts | 11 ++- e2e/oauth/account-select.spec.ts | 124 +++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 e2e/oauth/account-select.spec.ts diff --git a/actions/oauth/account-select.ts b/actions/oauth/account-select.ts index 8595387..11a0f79 100644 --- a/actions/oauth/account-select.ts +++ b/actions/oauth/account-select.ts @@ -9,6 +9,7 @@ export async function switchAccount( await destroySession(); const searchParams = new URLSearchParams(oauthParams); + searchParams.set("fresh_login", "true"); const loginUrl = `/login?redirect=/api/oauth/authorize?${searchParams.toString()}`; redirect(loginUrl); } diff --git a/app/api/oauth/authorize/route.ts b/app/api/oauth/authorize/route.ts index 56e237e..97b5b70 100644 --- a/app/api/oauth/authorize/route.ts +++ b/app/api/oauth/authorize/route.ts @@ -100,18 +100,23 @@ export async function GET(request: NextRequest) { const session = await getSession(); if (!session.isLoggedIn || !session.userId) { // Store OAuth params in session and redirect to login + // Add fresh_login flag so we skip account selection after login const loginUrl = new URL("/login", request.url); + const redirectParams = new URLSearchParams(searchParams.toString()); + redirectParams.set("fresh_login", "true"); loginUrl.searchParams.set( "redirect", - `/api/oauth/authorize?${searchParams.toString()}` + `/api/oauth/authorize?${redirectParams.toString()}` ); return NextResponse.redirect(loginUrl); } // If user is already signed in and hasn't confirmed their account, - // redirect to account selection page + // redirect to account selection page. + // Skip account selection if user just logged in (fresh_login=true) const accountConfirmed = searchParams.get("account_confirmed"); - if (!accountConfirmed) { + const freshLogin = searchParams.get("fresh_login"); + if (!accountConfirmed && !freshLogin) { const accountSelectUrl = new URL("/oauth/account-select", request.url); accountSelectUrl.searchParams.set("client_id", client_id); accountSelectUrl.searchParams.set("redirect_uri", redirect_uri); diff --git a/e2e/oauth/account-select.spec.ts b/e2e/oauth/account-select.spec.ts new file mode 100644 index 0000000..b6ec498 --- /dev/null +++ b/e2e/oauth/account-select.spec.ts @@ -0,0 +1,124 @@ +import { test, expect } from "@playwright/test"; + +const ADMIN_PASSWORD = "e2e-test-admin-password"; + +test.describe("OAuth Account Selection", () => { + let clientId: string; + + test.beforeAll(async ({ browser }, testInfo) => { + const context = await browser.newContext(); + const page = await context.newPage(); + + // Login as admin to create OAuth client + await page.goto("/admin"); + await page.getByLabel("Admin Password").fill(ADMIN_PASSWORD); + await page.getByRole("button", { name: "Sign in with Password" }).click(); + await expect(page).toHaveURL("/admin/dashboard"); + + // Create OAuth client + await page.goto("/admin/dashboard/clients/new"); + await page.getByLabel("Application Name").fill("Account Select Test App"); + await page.getByTestId("redirect-uri-0").fill("http://localhost:3001/callback"); + await page.getByTestId("profile").click(); + await page.getByTestId("email").click(); + + await page.getByRole("button", { name: "Create Application" }).click(); + await expect(page.getByText("Client Created Successfully")).toBeVisible(); + + clientId = await page.getByTestId("client-id-display").inputValue(); + + await page.close(); + await context.close(); + }); + + function buildAuthorizeUrl(clientId: string, state?: string) { + const codeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"; + return ( + `/api/oauth/authorize?` + + `client_id=${clientId}&` + + `redirect_uri=${encodeURIComponent("http://localhost:3001/callback")}&` + + `response_type=code&` + + `scope=openid%20profile%20email&` + + `state=${state || "test-state"}&` + + `code_challenge=${codeChallenge}&` + + `code_challenge_method=S256` + ); + } + + test("should NOT show account selection on first-time sign-in", async ({ + page, + }, testInfo) => { + // Create a new user for this test + const user = { + email: `acct-sel-first-${Date.now()}-${testInfo.parallelIndex}@example.com`, + password: "TestPassword123!", + displayName: "First Sign In User", + }; + + // Register the user + await page.goto("/register"); + await page.getByLabel("Display Name").fill(user.displayName); + await page.getByLabel("Email").fill(user.email); + await page.getByLabel("Password").fill(user.password); + await page.getByRole("button", { name: "Create account" }).click(); + await expect(page).toHaveURL("/account"); + + // Logout and wait for redirect to login page + await page.getByRole("button", { name: "Avatar" }).click(); + await page.getByRole("button", { name: /sign out/i }).click(); + await expect(page).toHaveURL("/login"); + + // Now start OAuth flow without being logged in + await page.goto(buildAuthorizeUrl(clientId)); + + // Should redirect to login page + await expect(page).toHaveURL(/\/login/); + + // Login + await page.getByLabel("Email").fill(user.email); + await page.getByLabel("Password").fill(user.password); + await page.getByRole("button", { name: "Sign in", exact: true }).click(); + + // Should go directly to consent page WITHOUT showing account selection + await expect(page).toHaveURL(/\/oauth\/authorize/); + // Verify it's the consent page, not the account select page + expect(page.url()).not.toContain("/oauth/account-select"); + await expect( + page.getByRole("heading", { name: "Account Select Test App" }) + ).toBeVisible(); + }); + + test("should show account selection when user is already signed in", async ({ + page, + }, testInfo) => { + // Create a new user for this test + const user = { + email: `acct-sel-existing-${Date.now()}-${testInfo.parallelIndex}@example.com`, + password: "TestPassword123!", + displayName: "Already Signed In User", + }; + + // Register the user (auto-logs in) + await page.goto("/register"); + await page.getByLabel("Display Name").fill(user.displayName); + await page.getByLabel("Email").fill(user.email); + await page.getByLabel("Password").fill(user.password); + await page.getByRole("button", { name: "Create account" }).click(); + await expect(page).toHaveURL("/account"); + + // Now start OAuth flow while already logged in + await page.goto(buildAuthorizeUrl(clientId)); + + // Should show account selection page + await expect(page).toHaveURL(/\/oauth\/account-select/); + await expect(page.getByText("Choose an account")).toBeVisible(); + await expect(page.getByText(user.email)).toBeVisible(); + + // Click continue with current account + await page.getByTestId("continue-as-current").click(); + + // Should proceed to consent page + await expect(page).toHaveURL(/\/oauth\/authorize/); + expect(page.url()).not.toContain("/oauth/account-select"); + }); +});