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:
committed by
GitHub
parent
6060243616
commit
031f7dcd0c
@@ -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', () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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('/');
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user