-
Notifications
You must be signed in to change notification settings - Fork 2
Add SSO redirect logic to requireRole middleware (VIBE-240) #121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
**Problem:** The requireRole() middleware was always redirecting unauthenticated users to /sign-in, regardless of whether SSO was configured. This caused admin dashboard pages (/admin-dashboard, /system-admin-dashboard) to redirect to the account selection page instead of Azure AD SSO login, breaking the SSO authentication flow. **Solution:** Updated both requireRole() and requireAuth() middlewares to check: 1. If the requested URL is an admin page 2. If SSO is configured (ENABLE_SSO=true) 3. Redirect to /login (Azure AD) for admin pages when SSO enabled 4. Redirect to /sign-in (account selection) for non-admin pages or when SSO disabled **Changes:** - Added SSO-aware redirect logic to requireRole() and requireAuth() - Added unit tests for new authentication redirect behavior - Added requireAuth() middleware to account-home page - Added CFT IDAM authentication setup to account-home E2E tests - Consolidated SSO and CFT IDAM test files for better organization **Test Results:** ✅ All 247 E2E tests passing - SSO tests: 7/7 ✅ - CFT IDAM tests: 10/10 ✅ - Account Home tests: 42/42 ✅ (previously 0/42 ❌) - All other tests: 188/188 ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
WalkthroughIntroduces a centralized redirect helper for unauthenticated requests and integrates it into authentication/authorization middleware and tests, requires auth on account-home, removes several legacy E2E test files while adding consolidated SSO/CFT suites, and adds/uses new E2E login helpers. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Browser
participant App
participant RedirectHelper as redirectUnauthenticated
participant SSOConfig as isSsoConfigured()
User->>Browser: Request protected URL (e.g. /admin-dashboard or /account-home)
Browser->>App: GET request
App->>RedirectHelper: redirectUnauthenticated(req, res)
RedirectHelper->>App: save req.session.returnTo
RedirectHelper->>SSOConfig: isSsoConfigured()
alt Admin page AND SSO configured
RedirectHelper->>Browser: redirect to /login
Note right of Browser: SSO (Azure AD) flow continues
else Not admin OR SSO not configured
RedirectHelper->>Browser: redirect to /sign-in
Note right of Browser: local CFT IDAM flow continues
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
e2e-tests/tests/sso/sso.spec.ts (1)
25-63: Consider test.each pattern to reduce duplication.The Local Admin and CTSC Admin tests follow identical patterns. While test isolation is valuable in e2e testing, you could reduce duplication using Playwright's
test.eachpattern:+ const adminRoles = [ + { role: 'Local Admin', emailEnv: 'SSO_TEST_LOCAL_ADMIN_EMAIL', passwordEnv: 'SSO_TEST_LOCAL_ADMIN_PASSWORD' }, + { role: 'CTSC Admin', emailEnv: 'SSO_TEST_CTSC_ADMIN_EMAIL', passwordEnv: 'SSO_TEST_CTSC_ADMIN_PASSWORD' } + ]; + + for (const { role, emailEnv, passwordEnv } of adminRoles) { + test(`${role} can login via SSO and access admin dashboard only`, async ({ page }) => { + await page.goto('/admin-dashboard'); + await expect(page).toHaveURL(/login.microsoftonline.com/); + await loginWithSSO(page, process.env[emailEnv]!, process.env[passwordEnv]!); + await expect(page).toHaveURL('/admin-dashboard'); + await assertAuthenticated(page); + + await page.goto('/system-admin-dashboard'); + await expect(page).toHaveURL('/admin-dashboard'); + + await page.goto('/account-home'); + await expect(page).toHaveURL('/admin-dashboard'); + }); + }e2e-tests/tests/cft-idam/cft-idam.spec.ts (1)
61-106: Consider splitting this test into focused scenarios.This test validates three distinct concerns:
- Language parameter preservation through CFT IDAM flow (lines 62-77)
- Query parameter preservation in CFT login (lines 79-83)
- Welsh content display on rejection page (lines 85-105)
For better test isolation, maintainability, and clearer failure messages, consider splitting into separate tests:
test('Language parameter is preserved through CFT IDAM redirect', async ({ page }) => { await page.goto('/sign-in?lng=cy'); const heading = page.getByRole('heading', { name: /sut hoffech chi fewngofnodi/i }); await expect(heading).toBeVisible(); const hmctsRadio = page.getByRole('radio', { name: /gyda chyfrif myhmcts/i }); await hmctsRadio.check(); const continueButton = page.getByRole('button', { name: /parhau/i }); await continueButton.click(); const currentUrl = page.url(); expect(currentUrl).toMatch(/lng=cy|ui_locales=cy/); }); test('Query parameters are preserved in CFT login redirect', async ({ page }) => { await page.goto('/cft-login?lng=cy&test=value'); await page.waitForLoadState('networkidle'); // More explicit than timeout const currentUrl = page.url(); expect(currentUrl).toMatch(/lng=cy|ui_locales=cy/); }); test('CFT rejected page displays Welsh content correctly', async ({ page }) => { await page.goto('/cft-rejected?lng=cy'); // ... rest of Welsh content tests });Also, consider replacing
await page.waitForTimeout(2000)on line 81 withawait page.waitForLoadState('networkidle')for more reliable synchronization.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
e2e-tests/tests/account-home.spec.ts(1 hunks)e2e-tests/tests/cft-idam/cft-idam-session.spec.ts(0 hunks)e2e-tests/tests/cft-idam/cft-idam.spec.ts(5 hunks)e2e-tests/tests/sso/sso-authorisation.spec.ts(0 hunks)e2e-tests/tests/sso/sso-login.spec.ts(0 hunks)e2e-tests/tests/sso/sso-session.spec.ts(0 hunks)e2e-tests/tests/sso/sso.spec.ts(1 hunks)libs/auth/src/middleware/authenticate.test.ts(3 hunks)libs/auth/src/middleware/authenticate.ts(2 hunks)libs/auth/src/middleware/authorise.ts(3 hunks)libs/verified-pages/src/pages/account-home/index.ts(2 hunks)
💤 Files with no reviewable changes (4)
- e2e-tests/tests/sso/sso-authorisation.spec.ts
- e2e-tests/tests/cft-idam/cft-idam-session.spec.ts
- e2e-tests/tests/sso/sso-session.spec.ts
- e2e-tests/tests/sso/sso-login.spec.ts
🧰 Additional context used
🧬 Code graph analysis (6)
libs/verified-pages/src/pages/account-home/index.ts (2)
libs/auth/src/middleware/authenticate.ts (1)
requireAuth(11-31)libs/auth/src/middleware/authorise.ts (1)
blockUserAccess(58-77)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (1)
e2e-tests/utils/cft-idam-helpers.ts (1)
loginWithCftIdam(10-41)
libs/auth/src/middleware/authenticate.ts (1)
libs/auth/src/index.ts (1)
isSsoConfigured(4-4)
e2e-tests/tests/sso/sso.spec.ts (1)
e2e-tests/utils/sso-helpers.ts (1)
loginWithSSO(10-46)
e2e-tests/tests/account-home.spec.ts (1)
e2e-tests/utils/cft-idam-helpers.ts (1)
loginWithCftIdam(10-41)
libs/auth/src/middleware/authenticate.test.ts (1)
libs/auth/src/middleware/authenticate.ts (1)
requireAuth(11-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: E2E Tests
🔇 Additional comments (13)
e2e-tests/tests/sso/sso.spec.ts (4)
1-23: LGTM! Comprehensive System Admin SSO flow test.The test correctly validates the System Admin authentication flow, dashboard access permissions, and the expected redirect from account-home to admin-dashboard for SSO users.
65-81: LGTM! Proper validation of edge cases.The tests correctly validate the SSO rejection flow for users without roles and the redirect behavior for unauthenticated users.
84-107: LGTM! Session lifecycle validation is thorough.The test properly validates session persistence across navigation, reload, and logout. The regex-based logout link selector (
text=/logout|sign out/i) provides flexibility for localization but assumes the link text follows this pattern.
109-138: LGTM! Excellent concurrent session validation.The test properly validates that multiple concurrent sessions from the same user are handled correctly, with appropriate context isolation and cleanup.
libs/verified-pages/src/pages/account-home/index.ts (1)
1-18: LGTM! Proper authentication gate added to account-home.The addition of
requireAuth()as the first middleware correctly protects the account-home page, ensuring users are authenticated before proceeding to role-based access control viablockUserAccess(). The middleware order is correct:
requireAuth()- Ensures authentication, redirects to /sign-in if unauthenticatedblockUserAccess()- Redirects SSO admin users to their admin dashboardgetHandler- Renders the page for authorized userslibs/auth/src/middleware/authorise.ts (1)
46-48: LGTM! Correct redirect for users without roles.The change to redirect users with no roles to
/sign-ininstead of/loginis correct. This allows users to potentially select a different authentication method at the account selection page.libs/auth/src/middleware/authenticate.ts (1)
11-30: LGTM! SSO-aware authentication redirect logic properly implemented.The middleware now correctly routes unauthenticated users based on SSO configuration and page type:
- Admin pages with SSO enabled →
/login(Azure AD)- All other cases →
/sign-in(account selection)The
req.session.returnTois properly preserved to redirect users back to their intended destination after authentication.Note: There's a related comment on
authorise.tsregarding code duplication of this redirect logic.e2e-tests/tests/account-home.spec.ts (1)
6-34: LGTM! Proper pre-authentication setup for protected page tests.The
beforeEachhook correctly authenticates users before each test, which is necessary since account-home now requires authentication via therequireAuth()middleware. The authentication flow is comprehensive and uses the sharedloginWithCftIdamhelper for consistency.Minor note: The first test (lines 31-34) correctly acknowledges that the page is already loaded from
beforeEach, but many subsequent tests still callawait page.goto("/account-home"). While redundant, this doesn't affect test correctness and may improve test independence.libs/auth/src/middleware/authenticate.test.ts (1)
1-133: LGTM! Comprehensive test coverage for SSO-aware authentication.The test suite now properly covers all authentication redirect scenarios:
- Authenticated users continue without redirect ✓
- Admin pages with SSO enabled redirect to
/login✓- Admin pages without SSO redirect to
/sign-in✓- Non-admin pages redirect to
/sign-inregardless of SSO configuration ✓All tests correctly verify that
req.session.returnTois preserved for post-authentication redirection. The mock setup withbeforeEachcleanup ensures test isolation.e2e-tests/tests/cft-idam/cft-idam.spec.ts (4)
108-125: LGTM! Proper error handling validation.The test correctly validates error scenarios in the CFT IDAM authorization flow, including missing and invalid authorization codes. The verification that protected resources redirect unauthenticated users to sign-in is also appropriate.
166-202: LGTM! Comprehensive session lifecycle validation.This test excellently validates the complete CFT IDAM session lifecycle from initial unauthenticated state through login, navigation, reload, and logout. The clear test phase comments and consistent use of helper functions make the test easy to understand and maintain.
204-244: LGTM! Proper concurrent session validation for CFT IDAM.The test correctly validates that multiple concurrent sessions from the same user work independently with CFT IDAM authentication. The test properly isolates contexts, verifies authentication state, and cleans up resources.
248-305: LGTM! Thorough validation of CFT rejected page.Both tests properly validate the rejected page:
- Content display and accessibility compliance
- Keyboard navigation functionality
The keyboard navigation test (lines 279-305) is particularly well-implemented, with a robust tab-until-found approach that handles variable page structure.
🎭 Playwright E2E Test Results265 tests 265 ✅ 17m 3s ⏱️ Results for commit 9963248. ♻️ This comment has been updated with latest results. |
**Problem:** The SSO-aware redirect logic was duplicated in both authenticate.ts and authorise.ts middlewares, increasing maintenance burden and risk of inconsistencies. **Solution:** - Created new redirect-helpers.ts with redirectUnauthenticated() function - Updated both authenticate.ts and authorise.ts to use the helper - Simplified middleware tests to mock the helper function directly - Added comprehensive unit tests for the redirect helper **Changes:** - **libs/auth/src/middleware/redirect-helpers.ts**: New helper function with SSO-aware redirect logic - **libs/auth/src/middleware/redirect-helpers.test.ts**: 11 comprehensive tests covering all redirect scenarios - **libs/auth/src/middleware/authenticate.ts**: Refactored to use helper - **libs/auth/src/middleware/authenticate.test.ts**: Simplified tests to mock helper - **libs/auth/src/middleware/authorise.ts**: Refactored to use helper - **libs/auth/src/middleware/authorise.test.ts**: Simplified tests to mock helper **Benefits:** - DRY: Single source of truth for redirect logic - Easier maintenance: Changes only need to be made in one place - Better testability: Redirect logic can be tested independently - Consistent behavior: Both middlewares use identical redirect logic **Test Results:** ✅ All 170 auth unit tests passing (including 11 new redirect helper tests) ✅ All 7 SSO E2E tests passing ✅ All 42 account-home E2E tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Replace permissive assertion that accepts both success and error states
with explicit IDAM redirect check. The previous test would pass even when
CFT IDAM was misconfigured, making it impossible to detect failures.
Changes:
- Use waitForLoadState('networkidle') instead of arbitrary timeout
- Assert redirect to IDAM URL instead of accepting error pages
- Update test name to reflect what is actually being validated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (2)
76-76: Useconstinstead ofletfor immutable variables.Both
currentUrldeclarations are only assigned once in their respective scopes and never reassigned, so they should be declared withconst.Apply this diff:
- let currentUrl = page.url(); + const currentUrl = page.url(); expect(currentUrl).toMatch(/lng=cy|ui_locales=cy/); // Test 2: CFT login preserves query parameters in redirect await page.goto('/cft-login?lng=cy&test=value'); await page.waitForLoadState('networkidle'); - currentUrl = page.url(); + const currentUrl = page.url(); expect(currentUrl).toMatch(/lng=cy|ui_locales=cy/);Also applies to: 82-82
117-117: Useconstinstead ofletfor immutable URL variables.The
urlvariable is only assigned once in each test segment (Test 2 and Test 3) and never reassigned, so it should be declared withconst.Apply this diff:
// Test 2: Authorization flow handles missing code parameter await page.goto('/cft-login/return'); await expect(page).toHaveURL(/sign-in.*error/); - let url = new URL(page.url()); + const url = new URL(page.url()); expect(url.searchParams.get('error')).toBeTruthy(); // Test 3: Authorization flow handles invalid code parameter await page.goto('/cft-login/return?code=invalid-code-12345'); await expect(page).toHaveURL(/sign-in.*error/); - url = new URL(page.url()); + const url = new URL(page.url()); expect(url.searchParams.get('error')).toBeTruthy();Also applies to: 123-123
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
e2e-tests/tests/cft-idam/cft-idam.spec.ts(5 hunks)libs/auth/src/middleware/authenticate.test.ts(2 hunks)libs/auth/src/middleware/authenticate.ts(2 hunks)libs/auth/src/middleware/authorise.test.ts(4 hunks)libs/auth/src/middleware/authorise.ts(3 hunks)libs/auth/src/middleware/redirect-helpers.test.ts(1 hunks)libs/auth/src/middleware/redirect-helpers.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- libs/auth/src/middleware/authenticate.ts
🧰 Additional context used
🧬 Code graph analysis (5)
libs/auth/src/middleware/redirect-helpers.test.ts (1)
libs/auth/src/middleware/redirect-helpers.ts (1)
redirectUnauthenticated(13-30)
libs/auth/src/middleware/authorise.test.ts (3)
libs/auth/src/middleware/authorise.ts (1)
requireRole(12-39)libs/account/src/roles.ts (1)
USER_ROLES(1-5)libs/auth/src/middleware/redirect-helpers.ts (1)
redirectUnauthenticated(13-30)
libs/auth/src/middleware/authenticate.test.ts (2)
libs/auth/src/middleware/redirect-helpers.ts (1)
redirectUnauthenticated(13-30)libs/auth/src/middleware/authenticate.ts (1)
requireAuth(11-19)
libs/auth/src/middleware/authorise.ts (1)
libs/auth/src/middleware/redirect-helpers.ts (1)
redirectUnauthenticated(13-30)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (1)
e2e-tests/utils/cft-idam-helpers.ts (1)
loginWithCftIdam(10-41)
🔇 Additional comments (16)
libs/auth/src/middleware/authorise.test.ts (3)
6-15: LGTM! Clean mock setup.The mock hoisting and beforeEach hook follow vitest best practices, ensuring proper test isolation.
34-67: LGTM! Tests properly verify the delegated redirect behavior.Both tests correctly assert that
redirectUnauthenticatedis called for unauthenticated scenarios and that the middleware chain is not continued.
136-157: LGTM! Redirect target updated correctly.The test correctly expects
/sign-infor users without roles, aligning with the centralized redirect logic.libs/auth/src/middleware/authorise.ts (2)
4-17: LGTM! Code duplication successfully eliminated.The refactoring addresses the past review feedback by delegating to the centralized
redirectUnauthenticatedhelper. This improves maintainability and ensures consistent redirect behavior across the codebase.
35-37: LGTM! Fallback redirect correctly updated.Users without roles are now redirected to
/sign-in(account selection), which is consistent with the centralized redirect logic for non-admin pages.libs/auth/src/middleware/redirect-helpers.ts (1)
1-30: LGTM! Excellent refactoring that centralizes redirect logic.The
redirectUnauthenticatedhelper successfully consolidates the SSO-aware redirect behavior:
- Clear JSDoc documentation
- Proper
returnTopreservation- Correct admin page detection using
startsWith- Appropriate SSO configuration check
- Clean early return pattern
This addresses the code duplication issue noted in past reviews and improves maintainability.
libs/auth/src/middleware/authenticate.test.ts (2)
5-14: LGTM! Proper mock setup.The mock hoisting and beforeEach hook follow vitest best practices for test isolation.
16-44: LGTM! Tests properly verify the middleware delegation.Both test cases correctly verify:
- Authenticated users continue through the middleware chain
- Unauthenticated users are redirected via
redirectUnauthenticatedlibs/auth/src/middleware/redirect-helpers.test.ts (3)
16-95: LGTM! Comprehensive test coverage for SSO-enabled scenarios.The tests thoroughly verify:
- Admin pages redirect to
/loginwhen SSO is configured- Non-admin pages redirect to
/sign-inregardless of SSO- Query parameters are preserved in
returnTo- Both redirect target and
returnToare correctly set
97-146: LGTM! Proper coverage for SSO-disabled scenarios.The tests correctly verify that all pages redirect to
/sign-inwhen SSO is not configured, maintaining consistent behavior for the account selection flow.
148-199: LGTM! Excellent edge case coverage.The tests verify important edge cases:
- Trailing slashes are handled correctly
- Hash fragments are preserved in
returnTo- Substring matches don't incorrectly trigger admin page detection (e.g.,
/not-admin-dashboard)This prevents potential bugs from URL variations.
e2e-tests/tests/cft-idam/cft-idam.spec.ts (5)
127-148: LGTM! Well-structured destination preservation test.This test properly validates the complete flow of accessing a protected resource, being redirected to sign-in, completing authentication, and being redirected back to the original destination. The test structure is clear and assertions are appropriate.
150-157: LGTM! Past review feedback properly addressed.The test now uses a strict assertion that only accepts successful IDAM redirects, replacing the previously permissive logic that accepted both success and error states. The addition of
waitForLoadState('networkidle')provides more explicit synchronization. This directly addresses the past review comment.
160-196: LGTM! Comprehensive session lifecycle test.This test provides excellent E2E coverage of the complete authentication lifecycle, from initial unauthenticated access through login, session persistence across navigation and reload, to logout. The clear Test 1-5 labels make the test easy to follow and maintain.
198-238: LGTM! Proper concurrent session testing.This test correctly uses separate browser contexts to validate concurrent session handling, properly cleans up resources by closing contexts, and verifies that both sessions remain independent and valid. The test follows Playwright best practices for multi-context scenarios.
273-299: LGTM! Good accessibility testing.This test provides valuable keyboard navigation coverage for the CFT rejected page. The loop-and-try approach to finding the focusable link is appropriate given the variable page structure, and the test properly verifies both focus state and Enter-key navigation.
Replace permissive CFT login endpoint assertion that accepts both
success and error states with explicit IDAM redirect check. Replace
arbitrary timeout with targeted wait for navigation completion.
Changes:
- Use waitForLoadState('networkidle') for CFT login endpoint test
- Assert redirect to IDAM URL instead of accepting error pages
- Replace waitForTimeout with waitForURL for query parameter test
- Add assertion for test parameter preservation
- Fix formatting in redirect-helpers.ts for linting compliance
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (4)
86-106: Consider moving Welsh rejected page validation to dedicated test.Test 3 validates Welsh content on the CFT rejected page, which is outside the scope of "Language and query parameters are preserved through CFT IDAM flow." This makes the test harder to maintain and understand.
Consider moving this validation to a new test in the "CFT IDAM Rejected Page" describe block:
test('CFT rejected page displays Welsh content correctly', async ({ page }) => { await page.goto('/cft-rejected?lng=cy'); // Verify language toggle shows English const languageToggle = page.locator('.language'); await expect(languageToggle).toContainText('English'); // Check for Welsh content const welshHeading = page.locator('h1'); await expect(welshHeading).toBeVisible(); // Check return link still works const signInLink = page.getByRole('link', { name: /yn ôl.*fewngofnodi|return.*sign/i }); await expect(signInLink).toHaveAttribute('href', '/sign-in'); // Run accessibility checks in Welsh const accessibilityScanResults = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']) .disableRules(['target-size', 'link-name']) .analyze(); expect(accessibilityScanResults.violations).toEqual([]); });This improves test organization and makes it easier to identify failures.
161-197: Consider splitting multi-step lifecycle test for easier debugging.This test validates 5 distinct behaviors in a single test case: unauthenticated redirect, login, navigation persistence, reload persistence, and logout. While this provides good end-to-end coverage, a failure in any step prevents subsequent steps from running, making it harder to identify multiple issues.
Consider splitting into focused tests while maintaining one comprehensive test:
test('Complete session lifecycle: login, navigation, reload, and logout', async ({ page }) => { // Keep existing comprehensive test as-is // ... }); test('Authenticated session persists across navigation', async ({ page }) => { // Abbreviated: login, then test navigation only }); test('Authenticated session persists after page reload', async ({ page }) => { // Abbreviated: login, then test reload only }); test('User can logout successfully', async ({ page }) => { // Abbreviated: login, then test logout only });This allows independent validation of each behavior while keeping the comprehensive lifecycle test for integration verification.
282-291: Retry loop could be fragile if page structure changes.The test uses a trial-and-error approach with up to 10 tab presses to find the sign-in link. While this simulates user behavior, it may become flaky if interactive elements are added or reordered.
Consider a more deterministic approach:
// Find all focusable elements const focusableElements = await page.locator('a, button, input, [tabindex]:not([tabindex="-1"])').all(); // Tab to each and check for sign-in link for (let i = 0; i < focusableElements.length; i++) { await page.keyboard.press('Tab'); const signInLink = page.getByRole('link', { name: /return to sign in page/i }); if (await signInLink.evaluate(el => el === document.activeElement)) { break; } } // Verify link is focused const signInLink = page.getByRole('link', { name: /return to sign in page/i }); await expect(signInLink).toBeFocused();Alternatively, keep the current implementation but reduce the iteration limit to match expected tab order.
110-113: ---Consider consolidating redundant protected resource redirect tests.
Lines 110–113 and 162–165 test identical behavior: accessing
/account-homewithout authentication redirects to sign-in. Both executepage.goto('/account-home'), check the same URL redirect, and callassertNotAuthenticated(page). While defense-in-depth testing has value, this specific assertion appears duplicated across two test suites. Consider whether a single authoritative test suffices, or document the intentional overlap if multiple layers are required.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
e2e-tests/tests/cft-idam/cft-idam.spec.ts(5 hunks)libs/auth/src/middleware/redirect-helpers.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- libs/auth/src/middleware/redirect-helpers.ts
🧰 Additional context used
🧬 Code graph analysis (1)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (1)
e2e-tests/utils/cft-idam-helpers.ts (1)
loginWithCftIdam(10-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: E2E Tests
🔇 Additional comments (2)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (2)
151-157: Past review comment successfully addressed.The test now uses
waitForLoadState('networkidle')and a strict URL assertion that requires a redirect to the CFT IDAM host, eliminating the permissive dual-pass condition from the previous implementation.
199-239: Good concurrent session test implementation.The test properly uses separate browser contexts to simulate multiple concurrent sessions and validates that both remain authenticated independently. This provides good coverage of session isolation.
The test mock for @hmcts/auth was incomplete, missing the requireAuth export which caused 15 tests to fail. This was a pre-existing issue discovered when running unit tests for ticket validation. Changes: - Add requireAuth mock that returns middleware calling next() - Update comment to reflect all three mocked functions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Replace waitForURL with ambiguous regex that could match the initial
page with waitForLoadState('networkidle') to ensure redirect completes
before assertions run.
The previous regex /idam-web-public\.aat\.platform\.hmcts\.net|cft-login/
could match the initial cft-login page before the redirect happened,
causing the test to proceed prematurely.
Changes:
- Use waitForLoadState('networkidle') instead of waitForURL regex
- Ensures all network activity and redirects complete before assertions
- Prevents race condition where test could pass/fail intermittently
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
libs/verified-pages/src/pages/account-home/index.test.ts (2)
4-19: LGTM! Mock implementation is correct and consistent.The
requireAuthmock follows the same pattern asblockUserAccessand correctly returns a no-op middleware for testing purposes. This allows the tests to focus on the handler logic without authentication concerns.Optionally, you could improve type safety by replacing
anytypes with proper Express types:- requireAuth: vi.fn(() => (_req: any, _res: any, next: any) => next()), - blockUserAccess: vi.fn(() => (_req: any, _res: any, next: any) => next()) + requireAuth: vi.fn(() => (_req: Request, _res: Response, next: NextFunction) => next()), + blockUserAccess: vi.fn(() => (_req: Request, _res: Response, next: NextFunction) => next())You would also need to import
NextFunctionfrom express:-import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express";
42-250: Consider adding middleware chain verification tests.The existing tests appropriately focus on handler behavior. However, you could optionally add a test to verify the middleware chain structure and order.
Example test:
it("should have correct middleware chain order", async () => { const { GET } = await import("./index.js"); expect(Array.isArray(GET)).toBe(true); expect(GET).toHaveLength(3); // requireAuth, blockUserAccess, handler // Verify middlewares are functions expect(typeof GET[0]).toBe("function"); expect(typeof GET[1]).toBe("function"); expect(typeof GET[2]).toBe("function"); });This ensures the middleware chain structure remains correct if refactoring occurs.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
libs/verified-pages/src/pages/account-home/index.test.ts(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: SonarQube Analysis
- GitHub Check: E2E Tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (2)
84-84: Consider using.toContain()for literal string matching.For checking the presence of the literal string
test=value, using.toContain('test=value')is more direct and consistent with the assertion style on line 83 than wrapping it in a regex with.toMatch().Apply this diff:
- expect(currentUrl).toMatch(/test=value/); + expect(currentUrl).toContain('test=value');
282-291: Consider a more robust approach to keyboard navigation testing.The loop with try-catch to find the focused element could be fragile if the page structure changes. While testing actual tab navigation is valuable, the current approach:
- Silently continues on timeout with an empty catch block
- Assumes the link is within 10 tab stops
- Makes debugging harder if the test fails
Consider one of these alternatives:
Option 1 (if testing actual tab order is essential):
// Find and focus on the sign-in link let focused = false; for (let i = 0; i < 10 && !focused; i++) { await page.keyboard.press('Tab'); const signInLink = page.getByRole('link', { name: /return to sign in page/i }); try { await expect(signInLink).toBeFocused({ timeout: 100 }); focused = true; } catch { - // Continue tabbing + // Continue tabbing if not focused yet } } + + // Ensure we actually found the focused element + expect(focused).toBe(true);Option 2 (if direct focus testing is acceptable):
// Directly focus the sign-in link and verify keyboard interaction const signInLink = page.getByRole('link', { name: /return to sign in page/i }); await signInLink.focus(); await expect(signInLink).toBeFocused(); // Press Enter to navigate await page.keyboard.press('Enter'); await expect(page).toHaveURL('/sign-in');
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
e2e-tests/tests/cft-idam/cft-idam.spec.ts(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (1)
e2e-tests/utils/cft-idam-helpers.ts (1)
loginWithCftIdam(10-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: E2E Tests
🔇 Additional comments (6)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (6)
109-126: LGTM!The test effectively validates error handling for protected resource access and invalid authorization flows with clear assertions.
128-149: LGTM!This test properly validates that the authentication flow preserves and redirects users back to their originally requested protected resource.
151-158: LGTM!The test now correctly uses a strict URL assertion and proper wait condition, addressing previous review feedback.
160-197: LGTM!The comprehensive session lifecycle test appropriately bundles related scenarios in sequence, validating authentication state from initial access through logout.
199-239: LGTM!The concurrent sessions test properly validates multi-context authentication and correctly manages browser context lifecycle with cleanup.
243-272: LGTM!The test thoroughly validates content and accessibility of the CFT rejected page with appropriate accessibility rule exclusions that are documented.
Remove incorrect expectation that arbitrary query parameters are preserved through CFT IDAM redirect. The implementation only preserves the language parameter (lng -> ui_locales), which is the correct behavior. Changes: - Remove test=value parameter from test URL - Remove assertion checking for test=value in redirected URL - Simplify comment to reflect actual behavior - Update assertion to only check for ui_locales=cy The test now correctly validates that only the language parameter is preserved during the IDAM redirect flow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (1)
79-84: Clarify test intent and fix assertion inconsistency.Test 2 navigates to
/cft-login?lng=cyand checks the URL after network idle, but it doesn't complete the IDAM login flow (unlike theloginWithCftIdamhelper which waits for the full redirect). It's unclear what this test is validating:
- If testing the redirect URL parameter transformation, the test should explicitly wait for the IDAM redirect:
await page.waitForURL(/idam-web-public\.aat\.platform\.hmcts\.net/)- The assertion at line 83 only checks for
ui_locales=cy, but Test 1 (line 77) checks for bothlng=cyandui_locales=cy. For consistency, line 83 should also check that the originallngparameter is preserved or transformed.Consider this refactor to make the test intent clearer:
- // Test 2: CFT login preserves language parameter in redirect + // Test 2: CFT login transforms language parameter for IDAM redirect await page.goto('/cft-login?lng=cy'); - await page.waitForLoadState('networkidle'); + await page.waitForURL(/idam-web-public\.aat\.platform\.hmcts\.net/); currentUrl = page.url(); - expect(currentUrl).toMatch(/ui_locales=cy/); + expect(currentUrl).toMatch(/lng=cy|ui_locales=cy/);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
e2e-tests/tests/cft-idam/cft-idam.spec.ts(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (1)
e2e-tests/utils/cft-idam-helpers.ts (1)
loginWithCftIdam(10-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: SonarQube Analysis
- GitHub Check: E2E Tests
🔇 Additional comments (7)
e2e-tests/tests/cft-idam/cft-idam.spec.ts (7)
108-125: LGTM!The test effectively consolidates three authorization error scenarios with clear assertions. The error parameter validation and redirect checks are appropriate.
127-148: LGTM!Excellent test coverage for redirect preservation after login. This ensures users are returned to their originally requested protected resource, which is critical for user experience.
150-157: LGTM!The test now correctly asserts a strict redirect to the CFT IDAM host with proper network idle wait, addressing previous review concerns.
160-196: LGTM!Comprehensive session lifecycle test with excellent coverage of authentication persistence across navigations and page reloads. The clear test structure with numbered comments makes it easy to understand each scenario.
198-238: LGTM!Well-structured test for concurrent session handling. Properly creates isolated browser contexts, verifies both sessions remain authenticated (confirming the system allows multiple concurrent sessions), and includes proper cleanup.
242-271: LGTM!Thorough validation of the rejected page content and accessibility. The accessibility scan with documented rule exclusions is appropriate, and the content checks cover all key elements.
273-299: LGTM!The keyboard navigation test uses a pragmatic approach with a tabbing loop to handle variable element order. The 10-iteration limit is reasonable for this simple page, and the 100ms timeout on focus assertions helps prevent flakiness.



Problem:
The requireRole() middleware was always redirecting unauthenticated users to /sign-in, regardless of whether SSO was configured. This caused admin dashboard pages (/admin-dashboard, /system-admin-dashboard) to redirect to the account selection page instead of Azure AD SSO login, breaking the SSO authentication flow.
Solution:
Updated both requireRole() and requireAuth() middlewares to check:
Changes:
Jira link
https://tools.hmcts.net/jira/browse/VIBE-240
Testing done
yes
Checklist
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.