-
This commit is contained in:
379
kms-frontend/src/components/TokenTesterCallback.tsx
Normal file
379
kms-frontend/src/components/TokenTesterCallback.tsx
Normal file
@ -0,0 +1,379 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Card,
|
||||
Button,
|
||||
Space,
|
||||
Typography,
|
||||
Alert,
|
||||
Row,
|
||||
Col,
|
||||
Tag,
|
||||
Spin,
|
||||
Result,
|
||||
Input,
|
||||
} from 'antd';
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
CopyOutlined,
|
||||
ArrowLeftOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { apiService } from '../services/apiService';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface CallbackData {
|
||||
token?: string;
|
||||
state?: string;
|
||||
error?: string;
|
||||
error_description?: string;
|
||||
}
|
||||
|
||||
interface VerificationResult {
|
||||
valid: boolean;
|
||||
permitted: boolean;
|
||||
user_id?: string;
|
||||
permissions: string[];
|
||||
permission_results?: Record<string, boolean>;
|
||||
expires_at?: string;
|
||||
max_valid_at?: string;
|
||||
token_type: string;
|
||||
claims?: Record<string, string>;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const TokenTesterCallback: React.FC = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [callbackData, setCallbackData] = useState<CallbackData>({});
|
||||
const [verificationResult, setVerificationResult] = useState<VerificationResult | null>(null);
|
||||
const [verificationError, setVerificationError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
parseCallbackData();
|
||||
}, [location]);
|
||||
|
||||
const parseCallbackData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Parse URL parameters
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
const data: CallbackData = {
|
||||
token: urlParams.get('token') || undefined,
|
||||
state: urlParams.get('state') || undefined,
|
||||
error: urlParams.get('error') || undefined,
|
||||
error_description: urlParams.get('error_description') || undefined,
|
||||
};
|
||||
|
||||
setCallbackData(data);
|
||||
|
||||
// If we have a token, try to verify it
|
||||
if (data.token && !data.error) {
|
||||
await verifyToken(data.token);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error parsing callback data:', error);
|
||||
setVerificationError('Failed to parse callback data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const verifyToken = async (token: string) => {
|
||||
try {
|
||||
// We need to extract app_id from the state or make a best guess
|
||||
// For now, we'll try to verify without specifying app_id
|
||||
// In a real implementation, the app_id should be included in the state parameter
|
||||
|
||||
// Try to get app_id from localStorage if it was stored during the test
|
||||
const testData = localStorage.getItem('token_tester_data');
|
||||
let appId = '';
|
||||
|
||||
if (testData) {
|
||||
try {
|
||||
const parsed = JSON.parse(testData);
|
||||
appId = parsed.app_id || '';
|
||||
} catch (e) {
|
||||
console.warn('Could not parse stored test data');
|
||||
}
|
||||
}
|
||||
|
||||
if (!appId) {
|
||||
// If we don't have app_id, we can't verify the token properly
|
||||
setVerificationError('Cannot verify token: Application ID not found in callback state');
|
||||
return;
|
||||
}
|
||||
|
||||
const verifyRequest = {
|
||||
app_id: appId,
|
||||
type: 'user',
|
||||
token: token,
|
||||
permissions: [], // We'll verify without specific permissions
|
||||
};
|
||||
|
||||
const result = await apiService.verifyToken(verifyRequest);
|
||||
setVerificationResult(result);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Token verification failed:', error);
|
||||
setVerificationError(
|
||||
error.response?.data?.message ||
|
||||
error.message ||
|
||||
'Token verification failed'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
};
|
||||
|
||||
const goBackToTester = () => {
|
||||
navigate('/token-tester');
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: '400px'
|
||||
}}>
|
||||
<Spin size="large" />
|
||||
<Text style={{ marginLeft: '16px' }}>Processing callback...</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<Title level={2}>Token Tester - Callback</Title>
|
||||
<Text type="secondary">
|
||||
Callback page for testing the login flow
|
||||
</Text>
|
||||
</div>
|
||||
<Button
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={goBackToTester}
|
||||
>
|
||||
Back to Tester
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Callback Status */}
|
||||
<Card title="Callback Status">
|
||||
{callbackData.error ? (
|
||||
<Alert
|
||||
message="Callback Error"
|
||||
description={`${callbackData.error}: ${callbackData.error_description || 'No description provided'}`}
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
) : callbackData.token ? (
|
||||
<Alert
|
||||
message="Callback Successful"
|
||||
description="Token received successfully from the login flow"
|
||||
type="success"
|
||||
showIcon
|
||||
/>
|
||||
) : (
|
||||
<Alert
|
||||
message="Invalid Callback"
|
||||
description="No token or error information found in callback URL"
|
||||
type="warning"
|
||||
showIcon
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Token Information */}
|
||||
{callbackData.token && (
|
||||
<Card title="Received Token">
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<div>
|
||||
<Text strong>Token:</Text>
|
||||
<TextArea
|
||||
value={callbackData.token}
|
||||
readOnly
|
||||
rows={4}
|
||||
style={{ fontFamily: 'monospace', fontSize: '12px', marginTop: '8px' }}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => copyToClipboard(callbackData.token!)}
|
||||
style={{ marginTop: '8px' }}
|
||||
>
|
||||
Copy Token
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{callbackData.state && (
|
||||
<div>
|
||||
<Text strong>State:</Text>
|
||||
<div style={{ marginTop: '4px' }}>
|
||||
<Text code>{callbackData.state}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Token Verification Results */}
|
||||
{verificationResult && (
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
{verificationResult.valid ? (
|
||||
<CheckCircleOutlined style={{ color: '#52c41a' }} />
|
||||
) : (
|
||||
<ExclamationCircleOutlined style={{ color: '#ff4d4f' }} />
|
||||
)}
|
||||
Token Verification Results
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
<Alert
|
||||
message={verificationResult.valid ? 'Token Valid' : 'Token Invalid'}
|
||||
description={verificationResult.valid
|
||||
? 'The token was successfully verified'
|
||||
: verificationResult.error || 'Token verification failed'
|
||||
}
|
||||
type={verificationResult.valid ? 'success' : 'error'}
|
||||
showIcon
|
||||
/>
|
||||
|
||||
{verificationResult.valid && (
|
||||
<div>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Card size="small" title="Token Information">
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<div>
|
||||
<Text strong>Token Type:</Text>
|
||||
<div>
|
||||
<Tag color="blue">{verificationResult.token_type.toUpperCase()}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{verificationResult.user_id && (
|
||||
<div>
|
||||
<Text strong>User ID:</Text>
|
||||
<div>{verificationResult.user_id}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{verificationResult.expires_at && (
|
||||
<div>
|
||||
<Text strong>Expires At:</Text>
|
||||
<div>{new Date(verificationResult.expires_at).toLocaleString()}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{verificationResult.max_valid_at && (
|
||||
<div>
|
||||
<Text strong>Max Valid Until:</Text>
|
||||
<div>{new Date(verificationResult.max_valid_at).toLocaleString()}</div>
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<Card size="small" title="Permissions">
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
{verificationResult.permissions && verificationResult.permissions.length > 0 ? (
|
||||
<div>
|
||||
<Text strong>Available Permissions:</Text>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
{verificationResult.permissions.map(permission => (
|
||||
<Tag key={permission} color="green" style={{ margin: '2px' }}>
|
||||
{permission}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Text type="secondary">No permissions available</Text>
|
||||
)}
|
||||
|
||||
{verificationResult.permission_results && Object.keys(verificationResult.permission_results).length > 0 && (
|
||||
<div style={{ marginTop: '16px' }}>
|
||||
<Text strong>Permission Check Results:</Text>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
{Object.entries(verificationResult.permission_results).map(([permission, granted]) => (
|
||||
<div key={permission} style={{ marginBottom: '4px' }}>
|
||||
<Tag color={granted ? 'green' : 'red'}>
|
||||
{permission}: {granted ? 'GRANTED' : 'DENIED'}
|
||||
</Tag>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{verificationResult.claims && Object.keys(verificationResult.claims).length > 0 && (
|
||||
<Card size="small" title="Token Claims" style={{ marginTop: '16px' }}>
|
||||
<Row gutter={8}>
|
||||
{Object.entries(verificationResult.claims).map(([key, value]) => (
|
||||
<Col span={8} key={key} style={{ marginBottom: '8px' }}>
|
||||
<Text strong>{key}:</Text>
|
||||
<div>{value}</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Verification Error */}
|
||||
{verificationError && (
|
||||
<Card title="Verification Error">
|
||||
<Alert
|
||||
message="Token Verification Failed"
|
||||
description={verificationError}
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* No Token or Error */}
|
||||
{!callbackData.token && !callbackData.error && (
|
||||
<Result
|
||||
status="warning"
|
||||
title="Invalid Callback"
|
||||
subTitle="This callback page expects to receive either a token or error information from the login flow."
|
||||
extra={
|
||||
<Button type="primary" onClick={goBackToTester}>
|
||||
Go Back to Token Tester
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TokenTesterCallback;
|
||||
Reference in New Issue
Block a user