1
0
mirror of https://github.com/quay/quay.git synced 2026-01-26 06:21:37 +03:00

[redhat-3.16] fix(web): redirect to username confirmation page after LDAP login (PROJQUAY-9735) (#4501)

fix(web): redirect to username confirmation page after LDAP login (PROJQUAY-9735)

When logging in with LDAP authentication, the new React UI was skipping the
username confirmation page and going directly to the Organizations page. This
fix ensures that users with the confirm_username prompt are redirected to the
/updateuser page after successful login.

Changes:
- Modified Signin.tsx to check for user prompts after successful login
- Added redirect to /updateuser if prompts exist
- Enhanced UpdateUser.tsx to honor quay.redirectAfterLoad for external logins
- Added Cypress e2e tests for username confirmation flow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Brady Pratt <bpratt@redhat.com>
Co-authored-by: Brady Pratt <bpratt@redhat.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
OpenShift Cherrypick Robot
2025-11-11 23:59:35 +01:00
committed by GitHub
parent 6060243616
commit 031f7dcd0c
3 changed files with 130 additions and 0 deletions

View File

@@ -234,6 +234,78 @@ describe('Signin page', () => {
// Should not redirect
cy.url().should('include', '/signin');
});
it('Redirects to username confirmation page when user has prompts', () => {
// Mock successful login
setupSuccessfulSignin();
// Mock user API to return user with confirm_username prompt
cy.intercept('GET', '/api/v1/user/', {
statusCode: 200,
body: {
anonymous: false,
username: 'test_ldap_user',
email: 'test@example.com',
verified: true,
prompts: ['confirm_username'],
organizations: [],
logins: [
{
service: 'ldap',
service_identifier: 'test_ldap_user',
metadata: {
service_username: 'test_ldap_user',
},
},
],
},
}).as('getUserWithPrompt');
// Fill and submit login form
cy.get('#pf-login-username-id').type('test_ldap_user');
cy.get('#pf-login-password-id').type('password');
cy.get('button[type=submit]').click();
// Wait for signin and user fetch
cy.wait('@signinSuccess');
cy.wait('@getCsrfToken');
cy.wait('@getUserWithPrompt');
// Should redirect to updateuser page for username confirmation
cy.url().should('include', '/updateuser');
});
it('Redirects to organization page when user has no prompts', () => {
// Mock successful login
setupSuccessfulSignin();
// Mock user API to return user without prompts
cy.intercept('GET', '/api/v1/user/', {
statusCode: 200,
body: {
anonymous: false,
username: 'user1',
email: 'user1@example.com',
verified: true,
prompts: [],
organizations: [],
logins: [],
},
}).as('getUserNoPrompt');
// Fill and submit login form
cy.get('#pf-login-username-id').type('user1');
cy.get('#pf-login-password-id').type('password');
cy.get('button[type=submit]').click();
// Wait for signin and user fetch
cy.wait('@signinSuccess');
cy.wait('@getCsrfToken');
cy.wait('@getUserNoPrompt');
// Should redirect to organization page
cy.url().should('include', '/organization');
});
});
describe('Forgot Password functionality', () => {

View File

@@ -26,6 +26,8 @@ import {useExternalLogins} from 'src/hooks/UseExternalLogins';
import {useExternalLoginAuth} from 'src/hooks/UseExternalLoginAuth';
import {ExternalLoginButton} from 'src/components/ExternalLoginButton';
import {LoginPageLayout} from 'src/components/LoginPageLayout';
import {useQueryClient} from '@tanstack/react-query';
import {fetchUser} from 'src/resources/UserResource';
type ViewType = 'signin' | 'forgotPassword';
@@ -48,6 +50,7 @@ export function Signin() {
const navigate = useNavigate();
const quayConfig = useQuayConfig();
const [searchParams] = useSearchParams();
const queryClient = useQueryClient();
const {
requestRecovery,
isLoading: sendingRecovery,
@@ -170,6 +173,29 @@ export function Signin() {
setAuthState((old) => ({...old, isSignedIn: true, username: username}));
await getCsrfToken();
GlobalAuthState.isLoggedIn = true;
// Fetch fresh user data to check for prompts
let user;
try {
user = await queryClient.fetchQuery(['user'], fetchUser);
} catch (fetchErr) {
// If fetching user fails, show error and stop navigation
setErr(
addDisplayError(
'Login successful but failed to load user data. Please refresh the page.',
fetchErr,
),
);
return;
}
// If user has prompts (e.g., confirm_username), redirect to updateuser
if (user.prompts && user.prompts.length > 0) {
navigate('/updateuser');
return;
}
// Otherwise, redirect to the intended destination
const redirectUrl = searchParams.get('redirect_url');
if (redirectUrl) {
window.location.href = redirectUrl;

View File

@@ -47,6 +47,38 @@ export default function UpdateUser() {
if (updatedUser?.prompts?.length) {
setIsUpdating(false);
} else {
// Check for stored redirect URL (set by external login flow)
const redirectUrl = localStorage.getItem('quay.redirectAfterLoad');
localStorage.removeItem('quay.redirectAfterLoad');
if (redirectUrl) {
// Validate redirect URL to prevent open redirect vulnerability
try {
// Allow relative paths (starting with /)
if (redirectUrl.startsWith('/')) {
window.location.href = redirectUrl;
return;
}
// For absolute URLs, validate they are same-origin
const url = new URL(redirectUrl);
if (url.origin === window.location.origin) {
window.location.href = redirectUrl;
return;
}
// Invalid URL - fall through to default navigation
console.warn(
'Ignoring redirect URL from different origin:',
redirectUrl,
);
} catch (err) {
// Invalid URL format - fall through to default navigation
console.warn('Invalid redirect URL format:', redirectUrl, err);
}
}
// Default navigation if no valid redirect URL
navigate('/');
}
},