This commit is contained in:
2025-07-29 15:36:25 -07:00
commit 0c481c7a0e
29 changed files with 6682 additions and 0 deletions

View File

@ -0,0 +1,250 @@
import React, { useState, useEffect } from 'react'
import { useSearchParams, useNavigate } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
import {
Container,
Paper,
Typography,
Box,
Button,
Grid,
Card,
CardContent,
List,
ListItem,
ListItemIcon,
ListItemText,
Chip,
Alert,
CircularProgress
} from '@mui/material'
import {
Security,
CheckCircle,
Cancel,
Person,
Email,
CalendarToday,
Visibility,
VisibilityOff
} from '@mui/icons-material'
import axios from 'axios'
const OAuthAuthorize = () => {
const { user } = useAuth()
const [searchParams] = useSearchParams()
const navigate = useNavigate()
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [clientInfo, setClientInfo] = useState(null)
const clientId = searchParams.get('client_id')
const redirectUri = searchParams.get('redirect_uri')
const scope = searchParams.get('scope')
const state = searchParams.get('state')
const responseType = searchParams.get('response_type')
useEffect(() => {
if (!user) {
navigate('/login')
return
}
if (!clientId || !redirectUri || responseType !== 'code') {
setError('无效的授权请求')
return
}
// 这里可以添加客户端信息获取逻辑
// 为了演示,我们使用默认信息
setClientInfo({
name: '第三方应用',
description: '请求访问您的账户信息',
scopes: scope ? scope.split(' ') : ['read', 'write']
})
}, [user, clientId, redirectUri, scope, responseType, navigate])
const handleAuthorize = async () => {
setLoading(true)
try {
const params = new URLSearchParams({
response_type: 'code',
client_id: clientId,
redirect_uri: redirectUri,
scope: scope || 'read write',
state: state || ''
})
const response = await axios.get(`/api/oauth/authorize?${params}`, {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`
}
})
if (response.data.success) {
const { redirect_url } = response.data.data
window.location.href = redirect_url
}
} catch (error) {
setError(error.response?.data?.message || '授权失败')
}
setLoading(false)
}
const handleDeny = () => {
// 拒绝授权,重定向回应用
const denyUrl = new URL(redirectUri)
denyUrl.searchParams.set('error', 'access_denied')
if (state) {
denyUrl.searchParams.set('state', state)
}
window.location.href = denyUrl.toString()
}
if (!user) {
return (
<Container maxWidth="sm">
<Box
sx={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<CircularProgress />
</Box>
</Container>
)
}
return (
<Container maxWidth="sm">
<Box
sx={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Paper
elevation={3}
sx={{
p: 4,
width: '100%',
maxWidth: 500
}}
>
<Box textAlign="center" mb={3}>
<Security sx={{ fontSize: 48, color: 'primary.main', mb: 2 }} />
<Typography variant="h4" component="h1" gutterBottom>
授权请求
</Typography>
<Typography variant="body2" color="text.secondary">
第三方应用请求访问您的账户
</Typography>
</Box>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{clientInfo && (
<>
{/* 应用信息 */}
<Card variant="outlined" sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
{clientInfo.name}
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
{clientInfo.description}
</Typography>
<Box mt={2}>
<Typography variant="body2" color="text.secondary" gutterBottom>
请求的权限
</Typography>
{clientInfo.scopes.map((scope) => (
<Chip
key={scope}
label={scope === 'read' ? '读取信息' : scope === 'write' ? '写入信息' : scope}
size="small"
sx={{ mr: 0.5, mb: 0.5 }}
color="primary"
variant="outlined"
/>
))}
</Box>
</CardContent>
</Card>
{/* 用户信息 */}
<Card variant="outlined" sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
您的账户信息
</Typography>
<List dense>
<ListItem>
<ListItemIcon>
<Person />
</ListItemIcon>
<ListItemText primary="用户名" secondary={user.username} />
</ListItem>
<ListItem>
<ListItemIcon>
<Email />
</ListItemIcon>
<ListItemText primary="邮箱" secondary={user.email} />
</ListItem>
<ListItem>
<ListItemIcon>
<CalendarToday />
</ListItemIcon>
<ListItemText
primary="注册时间"
secondary={new Date(user.created_at).toLocaleDateString()}
/>
</ListItem>
</List>
</CardContent>
</Card>
{/* 操作按钮 */}
<Grid container spacing={2}>
<Grid item xs={6}>
<Button
fullWidth
variant="outlined"
color="error"
startIcon={<Cancel />}
onClick={handleDeny}
disabled={loading}
>
拒绝
</Button>
</Grid>
<Grid item xs={6}>
<Button
fullWidth
variant="contained"
startIcon={<CheckCircle />}
onClick={handleAuthorize}
disabled={loading}
>
{loading ? '授权中...' : '授权'}
</Button>
</Grid>
</Grid>
</>
)}
</Paper>
</Box>
</Container>
)
}
export default OAuthAuthorize