mirror of
https://github.com/quay/quay.git
synced 2026-01-26 06:21:37 +03:00
fix(ui): OAuth token generation should not redirect to Old UI (PROJQUAY-9600) (#4406)
* Add nginx change to allow Oauth token generation in react * Create new component to handle OAuth token generation in new ui * Update cypress test for OAuth token generation * Fix cypress test Adds missing optional chaining + enable update-user.cy.ts tests * Add assignuser OAuth token generation + cypress test
This commit is contained in:
committed by
GitHub
parent
def6cc859c
commit
2d42e46eca
@@ -47,7 +47,7 @@ location / {
|
||||
# Show new UI if cookie or default is set to "true"/"react"
|
||||
if ($use_new_ui ~* "^(true|react)$") {
|
||||
# catch new static paths and direct to /index.html
|
||||
rewrite ^(/overview|/organization|/repository|/signin|/createaccount|/oauth-error|/oauth2/[^/]+/callback(/attach|/cli)?|/updateuser|/user/(.*)) /index.html break;
|
||||
rewrite ^(/overview|/organization|/repository|/signin|/createaccount|/oauth-error|/oauth/localapp|/oauth2/[^/]+/callback(/attach|/cli)?|/updateuser|/user/(.*)) /index.html break;
|
||||
}
|
||||
|
||||
# Show old UI if not using React
|
||||
@@ -57,7 +57,7 @@ location / {
|
||||
}
|
||||
|
||||
# Capture traffic that needs to go to web_app, see /web.py
|
||||
location ~* ^(/config|/csrf_token|/oauth1|/oauth2|/webhooks|/keys|/.well-known|/customtrigger|/authrepoemail|/confirm|/userfiles/(.*)|/health/(.*)|/status|/_internal_ping) {
|
||||
location ~* ^(/config|/csrf_token|/oauth/authorizeapp|/oauth/authorize/assignuser|/oauth1|/oauth2|/webhooks|/keys|/.well-known|/customtrigger|/authrepoemail|/confirm|/userfiles/(.*)|/health/(.*)|/status|/_internal_ping) {
|
||||
proxy_pass http://web_app_server;
|
||||
}
|
||||
|
||||
|
||||
@@ -613,7 +613,7 @@ describe('Organization OAuth Applications', () => {
|
||||
cy.get('[data-testid="generate-token-button"]').should('not.be.disabled');
|
||||
});
|
||||
|
||||
it('should open OAuth authorization in new tab when generating token', () => {
|
||||
it('should open OAuth authorization in popup window when generating token', () => {
|
||||
cy.visit('/organization/testorg?tab=OAuthApplications');
|
||||
cy.wait('@getOrg');
|
||||
cy.wait('@getOAuthApplications');
|
||||
@@ -668,7 +668,7 @@ describe('Organization OAuth Applications', () => {
|
||||
// Check form properties
|
||||
expect(capturedFormAction).to.include('/oauth/authorizeapp');
|
||||
expect(capturedFormMethod.toLowerCase()).to.equal('post');
|
||||
expect(capturedFormTarget).to.equal('_blank');
|
||||
expect(capturedFormTarget).to.equal('oauth_authorization');
|
||||
|
||||
// Check form data
|
||||
expect(capturedFormData.client_id).to.exist;
|
||||
@@ -677,6 +677,100 @@ describe('Organization OAuth Applications', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should display token in React modal after generation (not redirect to Angular)', () => {
|
||||
cy.visit('/organization/testorg?tab=OAuthApplications');
|
||||
cy.wait('@getOrg');
|
||||
cy.wait('@getOAuthApplications');
|
||||
|
||||
cy.contains('test-app').click();
|
||||
cy.get('[data-testid="generate-token-tab"]').click();
|
||||
cy.wait('@getCurrentUser');
|
||||
cy.wait('@getConfig');
|
||||
|
||||
// Select scopes
|
||||
cy.get('[data-testid="scope-repo:read"]').check();
|
||||
|
||||
// Click generate token to open modal
|
||||
cy.get('[data-testid="generate-token-button"]').click();
|
||||
|
||||
// Stub window.open and postMessage to simulate OAuth flow
|
||||
cy.window().then((win) => {
|
||||
// Stub window.open to return a fake popup
|
||||
const fakePopup = {
|
||||
closed: false,
|
||||
close: cy.stub(),
|
||||
};
|
||||
cy.stub(win, 'open').returns(fakePopup);
|
||||
|
||||
// Stub form submit to simulate OAuth callback
|
||||
const submitStub = cy.stub(win.HTMLFormElement.prototype, 'submit');
|
||||
submitStub.callsFake(function () {
|
||||
// Simulate OAuth callback by posting message
|
||||
setTimeout(() => {
|
||||
win.postMessage(
|
||||
{
|
||||
type: 'OAUTH_TOKEN_GENERATED',
|
||||
token: 'test-access-token-123456',
|
||||
scope: 'repo:read',
|
||||
state: null,
|
||||
},
|
||||
win.location.origin,
|
||||
);
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
// Click authorize in modal
|
||||
cy.get('[role="dialog"]').contains('Authorize Application').click();
|
||||
|
||||
// CRITICAL: Verify token modal appears in React UI
|
||||
cy.contains('Access Token Generated', {timeout: 5000}).should('exist');
|
||||
cy.contains('Your access token has been successfully generated').should(
|
||||
'exist',
|
||||
);
|
||||
|
||||
// Verify token is displayed in the ClipboardCopy input
|
||||
cy.get('.pf-v5-c-clipboard-copy input').should(
|
||||
'have.value',
|
||||
'test-access-token-123456',
|
||||
);
|
||||
|
||||
// Verify user is still in React UI (no redirect to Angular)
|
||||
cy.url().should('include', 'localhost');
|
||||
cy.url().should('not.include', '/oauth/localapp');
|
||||
cy.url().should('include', '/organization/testorg');
|
||||
});
|
||||
|
||||
it('should handle popup blocked scenario gracefully', () => {
|
||||
cy.visit('/organization/testorg?tab=OAuthApplications');
|
||||
cy.wait('@getOrg');
|
||||
cy.wait('@getOAuthApplications');
|
||||
|
||||
cy.contains('test-app').click();
|
||||
cy.get('[data-testid="generate-token-tab"]').click();
|
||||
cy.wait('@getCurrentUser');
|
||||
cy.wait('@getConfig');
|
||||
|
||||
// Select scopes
|
||||
cy.get('[data-testid="scope-repo:read"]').check();
|
||||
|
||||
// Click generate token to open modal
|
||||
cy.get('[data-testid="generate-token-button"]').click();
|
||||
|
||||
// Stub window.open to return null (popup blocked) BEFORE clicking authorize
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').returns(null);
|
||||
});
|
||||
|
||||
// Click authorize in modal
|
||||
cy.get('[role="dialog"]').contains('Authorize Application').click();
|
||||
|
||||
// Verify PatternFly warning alert appears (not browser alert)
|
||||
cy.contains(
|
||||
'Popup was blocked by your browser. Please allow popups for this site and try again.',
|
||||
).should('exist');
|
||||
});
|
||||
|
||||
it('should handle user assignment functionality', () => {
|
||||
cy.visit('/organization/testorg?tab=OAuthApplications');
|
||||
cy.wait('@getOrg');
|
||||
@@ -703,6 +797,82 @@ describe('Organization OAuth Applications', () => {
|
||||
'Assign token',
|
||||
);
|
||||
});
|
||||
|
||||
it('should assign token with correct parameter placement (query string)', () => {
|
||||
// Mock configuration with ASSIGN_OAUTH_TOKEN feature
|
||||
cy.intercept('GET', '/config', (req) =>
|
||||
req.reply((res) => {
|
||||
res.body.features = {
|
||||
...res.body.features,
|
||||
ASSIGN_OAUTH_TOKEN: true,
|
||||
};
|
||||
res.body.config.LOCAL_OAUTH_HANDLER = '/oauth/localapp';
|
||||
res.body.config.PREFERRED_URL_SCHEME = 'http';
|
||||
res.body.config.SERVER_HOSTNAME = 'localhost:8080';
|
||||
return res;
|
||||
}),
|
||||
).as('getConfig');
|
||||
|
||||
// Mock the assignuser OAuth endpoint with success response
|
||||
cy.intercept('POST', '/oauth/authorize/assignuser*', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
message: 'Token assigned successfully',
|
||||
},
|
||||
}).as('assignToken');
|
||||
|
||||
cy.visit('/organization/testorg?tab=OAuthApplications');
|
||||
cy.wait('@getOrg');
|
||||
cy.wait('@getOAuthApplications');
|
||||
|
||||
cy.contains('test-app').click();
|
||||
cy.get('[data-testid="generate-token-tab"]').click();
|
||||
cy.wait('@getCurrentUser');
|
||||
cy.wait('@getConfig');
|
||||
|
||||
// Click assign another user
|
||||
cy.get('[data-testid="assign-user-button"]').click();
|
||||
|
||||
// Search for user (user2 exists in seed data)
|
||||
cy.get('#entity-search-input').type('user2');
|
||||
|
||||
// Select user from results
|
||||
cy.contains('user2').click();
|
||||
|
||||
// Select scopes
|
||||
cy.get('[data-testid="scope-repo:read"]').check();
|
||||
cy.get('[data-testid="scope-repo:write"]').check();
|
||||
|
||||
// Click assign token button
|
||||
cy.get('[data-testid="generate-token-button"]').click();
|
||||
|
||||
// Authorization modal should appear
|
||||
cy.get('[role="dialog"]').should('be.visible');
|
||||
|
||||
// Click assign token button in modal to trigger fetch request
|
||||
cy.get('[role="dialog"]').contains('Assign token').click();
|
||||
|
||||
// Wait for the assign token request and verify it was called
|
||||
cy.wait('@assignToken').then((interception) => {
|
||||
// Verify query parameters are present in URL
|
||||
expect(interception.request.url).to.include('username=user2');
|
||||
expect(interception.request.url).to.include('client_id=TEST123');
|
||||
expect(interception.request.url).to.match(/scope=repo(%3A|:)read/);
|
||||
expect(interception.request.url).to.include('redirect_uri=');
|
||||
expect(interception.request.url).to.include('response_type=token');
|
||||
expect(interception.request.url).to.include('format=json');
|
||||
});
|
||||
|
||||
// Verify success alert appears (PatternFly alert)
|
||||
cy.contains('Token assigned successfully').should('exist');
|
||||
|
||||
// Verify modal is closed
|
||||
cy.get('[role="dialog"]').should('not.exist');
|
||||
|
||||
// Verify form is reset (user selection cleared)
|
||||
cy.get('[data-testid="cancel-assign-button"]').should('not.exist');
|
||||
cy.get('[data-testid="assign-user-button"]').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete OAuth Application', () => {
|
||||
|
||||
@@ -65,7 +65,7 @@ describe('Update User Component', () => {
|
||||
cy.get('button[type="submit"]').should('contain.text', 'Confirm Username');
|
||||
});
|
||||
|
||||
it.only('successfully confirms username', () => {
|
||||
it('successfully confirms username', () => {
|
||||
cy.intercept('GET', '/api/v1/user/', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {CreateAccount} from 'src/routes/CreateAccount/CreateAccount';
|
||||
import UpdateUser from 'src/routes/UpdateUser/UpdateUser';
|
||||
import {OAuthCallbackHandler} from 'src/routes/OAuthCallback/OAuthCallbackHandler';
|
||||
import {OAuthError} from 'src/routes/OAuthCallback/OAuthError';
|
||||
import OAuthLocalHandler from 'src/routes/OAuthLocalHandler';
|
||||
import {StandaloneMain} from 'src/routes/StandaloneMain';
|
||||
import {ThemeProvider} from './contexts/ThemeContext';
|
||||
|
||||
@@ -26,6 +27,7 @@ export default function App() {
|
||||
<Route path="/createaccount" element={<CreateAccount />} />
|
||||
<Route path="/updateuser" element={<UpdateUser />} />
|
||||
<Route path="/oauth-error" element={<OAuthError />} />
|
||||
<Route path="/oauth/localapp" element={<OAuthLocalHandler />} />
|
||||
<Route
|
||||
path="/oauth2/:provider/callback/*"
|
||||
element={<OAuthCallbackHandler />}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function QuaySidebar() {
|
||||
const quayConfig = useQuayConfig();
|
||||
const routes: SideNavProps[] = [
|
||||
{
|
||||
isSideNav: quayConfig?.config?.BRANDING.quay_io ? true : false,
|
||||
isSideNav: quayConfig?.config?.BRANDING?.quay_io ? true : false,
|
||||
navPath: NavigationPath.overviewList,
|
||||
title: 'Overview',
|
||||
component: <OverviewList />,
|
||||
|
||||
135
web/src/routes/OAuthLocalHandler/OAuthLocalHandler.tsx
Normal file
135
web/src/routes/OAuthLocalHandler/OAuthLocalHandler.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {useSearchParams, useNavigate} from 'react-router-dom';
|
||||
import {
|
||||
Alert,
|
||||
AlertVariant,
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Spinner,
|
||||
} from '@patternfly/react-core';
|
||||
import TokenDisplayModal from 'src/components/modals/TokenDisplayModal';
|
||||
|
||||
interface OAuthTokenData {
|
||||
access_token: string;
|
||||
scope: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
export default function OAuthLocalHandler() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const [tokenData, setTokenData] = useState<OAuthTokenData | null>(null);
|
||||
const [isTokenModalOpen, setIsTokenModalOpen] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Extract token from URL hash
|
||||
const hash = window.location.hash.substring(1);
|
||||
if (!hash) {
|
||||
setError('Authorization was cancelled');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(hash);
|
||||
const token = params.get('access_token');
|
||||
const scope = params.get('scope');
|
||||
const state = params.get('state');
|
||||
|
||||
if (!token) {
|
||||
setError('No access token received');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store token data
|
||||
const data: OAuthTokenData = {
|
||||
access_token: token,
|
||||
scope: scope || '',
|
||||
state: state || undefined,
|
||||
};
|
||||
setTokenData(data);
|
||||
|
||||
// Check if format=json requested (for API clients)
|
||||
if (searchParams.get('format') === 'json') {
|
||||
document.body.innerHTML = JSON.stringify({access_token: token});
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if opened in popup window
|
||||
if (window.opener && !window.opener.closed) {
|
||||
try {
|
||||
// Send token to parent window
|
||||
window.opener.postMessage(
|
||||
{
|
||||
type: 'OAUTH_TOKEN_GENERATED',
|
||||
token: token,
|
||||
scope: scope,
|
||||
state: state,
|
||||
},
|
||||
window.location.origin,
|
||||
);
|
||||
|
||||
// Close popup after message sent
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 500);
|
||||
} catch (err) {
|
||||
console.error('Failed to communicate with parent window:', err);
|
||||
// If postMessage fails, show modal in popup
|
||||
setIsTokenModalOpen(true);
|
||||
}
|
||||
} else {
|
||||
// Opened in same tab - show modal
|
||||
setIsTokenModalOpen(true);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}, [searchParams]);
|
||||
|
||||
const handleModalClose = () => {
|
||||
setIsTokenModalOpen(false);
|
||||
// Navigate back to home or organization page
|
||||
navigate('/organization');
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<Spinner />
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<Alert variant={AlertVariant.warning} title="Authorization Cancelled">
|
||||
{error}
|
||||
</Alert>
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
|
||||
if (!tokenData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scopes = tokenData.scope ? tokenData.scope.split(' ') : [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{isTokenModalOpen && (
|
||||
<TokenDisplayModal
|
||||
isOpen={isTokenModalOpen}
|
||||
onClose={handleModalClose}
|
||||
token={tokenData.access_token}
|
||||
applicationName="OAuth Application"
|
||||
scopes={scopes}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
1
web/src/routes/OAuthLocalHandler/index.ts
Normal file
1
web/src/routes/OAuthLocalHandler/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {default} from './OAuthLocalHandler';
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useState} from 'react';
|
||||
import React, {useState, useEffect, useCallback} from 'react';
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
@@ -18,10 +18,13 @@ import {useCurrentUser} from 'src/hooks/UseCurrentUser';
|
||||
import {useQuayConfig} from 'src/hooks/UseQuayConfig';
|
||||
import {FormCheckbox} from 'src/components/forms/FormCheckbox';
|
||||
import GenerateTokenAuthorizationModal from 'src/components/modals/GenerateTokenAuthorizationModal';
|
||||
import TokenDisplayModal from 'src/components/modals/TokenDisplayModal';
|
||||
import EntitySearch from 'src/components/EntitySearch';
|
||||
import {Entity} from 'src/resources/UserResource';
|
||||
import {GlobalAuthState} from 'src/resources/AuthResource';
|
||||
import {OAUTH_SCOPES, OAuthScope} from '../types';
|
||||
import {useAlerts} from 'src/hooks/UseAlerts';
|
||||
import {AlertVariant} from 'src/atoms/AlertState';
|
||||
|
||||
interface GenerateTokenTabProps {
|
||||
application: IOAuthApplication | null;
|
||||
@@ -36,8 +39,13 @@ export default function GenerateTokenTab(props: GenerateTokenTabProps) {
|
||||
const [customUser, setCustomUser] = useState(false);
|
||||
const [selectedUser, setSelectedUser] = useState<Entity | null>(null);
|
||||
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
|
||||
const [oauthPopup, setOauthPopup] = useState<Window | null>(null);
|
||||
const [generatedToken, setGeneratedToken] = useState<string | null>(null);
|
||||
const [generatedScopes, setGeneratedScopes] = useState<string[]>([]);
|
||||
const [isTokenDisplayModalOpen, setIsTokenDisplayModalOpen] = useState(false);
|
||||
const {user} = useCurrentUser();
|
||||
const quayConfig = useQuayConfig();
|
||||
const {addAlert} = useAlerts();
|
||||
|
||||
// Initialize form with all scopes set to false
|
||||
const defaultValues: GenerateTokenFormData = {};
|
||||
@@ -62,11 +70,8 @@ export default function GenerateTokenTab(props: GenerateTokenTabProps) {
|
||||
};
|
||||
|
||||
const getUrl = (path: string): string => {
|
||||
const scheme =
|
||||
quayConfig?.config?.PREFERRED_URL_SCHEME ||
|
||||
window.location.protocol.replace(':', '');
|
||||
const hostname =
|
||||
quayConfig?.config?.SERVER_HOSTNAME || window.location.host;
|
||||
const scheme = window.location.protocol.replace(':', '');
|
||||
const hostname = window.location.host;
|
||||
return `${scheme}://${hostname}${path}`;
|
||||
};
|
||||
|
||||
@@ -84,6 +89,7 @@ export default function GenerateTokenTab(props: GenerateTokenTabProps) {
|
||||
redirect_uri: getUrl(
|
||||
quayConfig.config.LOCAL_OAUTH_HANDLER || '/oauth/localapp',
|
||||
),
|
||||
format: 'json',
|
||||
});
|
||||
return getUrl(`/oauth/authorize/assignuser?${params.toString()}`);
|
||||
} else {
|
||||
@@ -127,38 +133,133 @@ export default function GenerateTokenTab(props: GenerateTokenTabProps) {
|
||||
setSelectedUser(null);
|
||||
};
|
||||
|
||||
const handleAuthModalConfirm = () => {
|
||||
// Use POST form submission for both assignment and generation
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = generateUrl();
|
||||
form.target = '_blank';
|
||||
form.style.display = 'none';
|
||||
const handleOAuthMessage = useCallback(
|
||||
(event: MessageEvent) => {
|
||||
// Verify origin for security
|
||||
if (event.origin !== window.location.origin) {
|
||||
console.warn('Received message from unexpected origin:', event.origin);
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract URL parameters and add them as form fields
|
||||
const url = new URL(generateUrl());
|
||||
url.searchParams.forEach((value, key) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = key;
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
});
|
||||
if (event.data.type === 'OAUTH_TOKEN_GENERATED') {
|
||||
setGeneratedToken(event.data.token);
|
||||
setGeneratedScopes(event.data.scope?.split(' ') || []);
|
||||
setIsTokenDisplayModalOpen(true);
|
||||
setIsAuthModalOpen(false);
|
||||
|
||||
// Add CSRF token for POST request
|
||||
if (GlobalAuthState.csrfToken) {
|
||||
const csrfInput = document.createElement('input');
|
||||
csrfInput.type = 'hidden';
|
||||
csrfInput.name = '_csrf_token';
|
||||
csrfInput.value = GlobalAuthState.csrfToken;
|
||||
form.appendChild(csrfInput);
|
||||
}
|
||||
// Clean up popup
|
||||
if (oauthPopup && !oauthPopup.closed) {
|
||||
oauthPopup.close();
|
||||
}
|
||||
setOauthPopup(null);
|
||||
}
|
||||
},
|
||||
[oauthPopup],
|
||||
);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
document.body.removeChild(form);
|
||||
useEffect(() => {
|
||||
window.addEventListener('message', handleOAuthMessage);
|
||||
return () => {
|
||||
window.removeEventListener('message', handleOAuthMessage);
|
||||
};
|
||||
}, [handleOAuthMessage]);
|
||||
|
||||
const handleAuthModalConfirm = async () => {
|
||||
setIsAuthModalOpen(false);
|
||||
|
||||
const fullUrl = new URL(generateUrl());
|
||||
const isAssignmentFlow = fullUrl.pathname.includes(
|
||||
'/oauth/authorize/assignuser',
|
||||
);
|
||||
|
||||
if (isAssignmentFlow) {
|
||||
const formData = new FormData();
|
||||
if (GlobalAuthState.csrfToken) {
|
||||
formData.append('_csrf_token', GlobalAuthState.csrfToken);
|
||||
}
|
||||
|
||||
try {
|
||||
// Use relative URL for fetch to ensure proper proxy handling
|
||||
const response = await fetch(fullUrl.pathname + fullUrl.search, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to assign token');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
addAlert({
|
||||
variant: AlertVariant.Success,
|
||||
title: data.message || 'Token assigned successfully',
|
||||
});
|
||||
setCustomUser(false);
|
||||
setSelectedUser(null);
|
||||
} catch (error) {
|
||||
console.error('Error assigning token:', error);
|
||||
addAlert({
|
||||
variant: AlertVariant.Failure,
|
||||
title: 'Failed to assign token. Please try again.',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.target = 'oauth_authorization';
|
||||
form.style.display = 'none';
|
||||
form.action = fullUrl.pathname;
|
||||
|
||||
fullUrl.searchParams.forEach((value, key) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = key;
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
if (GlobalAuthState.csrfToken) {
|
||||
const csrfInput = document.createElement('input');
|
||||
csrfInput.type = 'hidden';
|
||||
csrfInput.name = '_csrf_token';
|
||||
csrfInput.value = GlobalAuthState.csrfToken;
|
||||
form.appendChild(csrfInput);
|
||||
}
|
||||
|
||||
const width = 600;
|
||||
const height = 700;
|
||||
const left = window.screen.width / 2 - width / 2;
|
||||
const top = window.screen.height / 2 - height / 2;
|
||||
|
||||
const popup = window.open(
|
||||
'',
|
||||
'oauth_authorization',
|
||||
`width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes`,
|
||||
);
|
||||
|
||||
if (!popup || popup.closed || typeof popup.closed === 'undefined') {
|
||||
addAlert({
|
||||
variant: AlertVariant.Warning,
|
||||
title:
|
||||
'Popup was blocked by your browser. Please allow popups for this site and try again.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setOauthPopup(popup);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
document.body.removeChild(form);
|
||||
|
||||
const checkPopupClosed = setInterval(() => {
|
||||
if (popup.closed) {
|
||||
clearInterval(checkPopupClosed);
|
||||
setOauthPopup(null);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAuthModalClose = () => {
|
||||
@@ -294,6 +395,20 @@ export default function GenerateTokenTab(props: GenerateTokenTabProps) {
|
||||
targetUsername={selectedUser?.name}
|
||||
/>
|
||||
)}
|
||||
|
||||
{generatedToken && (
|
||||
<TokenDisplayModal
|
||||
isOpen={isTokenDisplayModalOpen}
|
||||
onClose={() => {
|
||||
setIsTokenDisplayModalOpen(false);
|
||||
setGeneratedToken(null);
|
||||
setGeneratedScopes([]);
|
||||
}}
|
||||
token={generatedToken}
|
||||
applicationName={application.name}
|
||||
scopes={generatedScopes}
|
||||
/>
|
||||
)}
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Dotenv = require('dotenv-webpack');
|
||||
const {merge} = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
const HOST = process.env.HOST || 'localhost';
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
const PORT = process.env.PORT || '9000';
|
||||
|
||||
module.exports = merge(common('development'), {
|
||||
@@ -32,6 +32,10 @@ module.exports = merge(common('development'), {
|
||||
target: 'http://localhost:8080',
|
||||
logLevel: 'debug',
|
||||
},
|
||||
'/oauth': {
|
||||
target: 'http://localhost:8080',
|
||||
logLevel: 'debug',
|
||||
},
|
||||
},
|
||||
},
|
||||
module: {
|
||||
|
||||
Reference in New Issue
Block a user