Files
pdnode-account/routes/oauth-clients.js
2025-07-29 15:36:25 -07:00

349 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const { body, validationResult } = require('express-validator');
const OAuthClient = require('../models/OAuthClient');
const { authenticateToken } = require('../middleware/auth');
const router = express.Router();
// 验证中间件
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: '输入验证失败',
errors: errors.array().map(error => ({
field: error.path,
message: error.msg
}))
});
}
next();
};
// 自定义URL验证函数
const isValidRedirectUri = (value) => {
console.log('验证URL:', value);
try {
const url = new URL(value);
console.log('URL解析结果:', {
protocol: url.protocol,
hostname: url.hostname,
pathname: url.pathname,
isValid: (url.protocol === 'http:' || url.protocol === 'https:') &&
url.hostname &&
url.pathname
});
// 允许http和https协议包括localhost
return (url.protocol === 'http:' || url.protocol === 'https:') &&
url.hostname &&
url.pathname;
} catch (error) {
console.log('URL验证失败:', value, error.message);
return false;
}
};
// 创建OAuth客户端验证规则
const createClientValidation = [
body('name')
.isLength({ min: 1, max: 100 })
.withMessage('应用名称长度必须在1-100个字符之间'),
body('description')
.optional()
.isLength({ max: 500 })
.withMessage('描述长度不能超过500个字符'),
body('redirect_uris')
.isArray({ min: 1 })
.withMessage('至少需要一个重定向URI'),
body('scopes')
.optional()
.isArray()
.withMessage('权限范围必须是数组格式')
];
// 自定义验证中间件
const validateRedirectUris = (req, res, next) => {
const { redirect_uris } = req.body;
if (!Array.isArray(redirect_uris)) {
return res.status(400).json({
success: false,
message: 'redirect_uris必须是数组'
});
}
for (let i = 0; i < redirect_uris.length; i++) {
if (!isValidRedirectUri(redirect_uris[i])) {
return res.status(400).json({
success: false,
message: '输入验证失败',
errors: [{
field: `redirect_uris[${i}]`,
message: '重定向URI必须是有效的URL'
}]
});
}
}
next();
};
// 1. 创建OAuth客户端
router.post('/clients', authenticateToken, createClientValidation, validateRedirectUris, handleValidationErrors, async (req, res) => {
try {
const { name, description, redirect_uris, scopes } = req.body;
const userId = req.user.userId;
const clientData = {
name,
description: description || '',
redirectUris: redirect_uris,
scopes: scopes || ['read', 'write'],
userId
};
const newClient = await OAuthClient.create(clientData);
res.status(201).json({
success: true,
message: 'OAuth客户端创建成功',
data: {
client_id: newClient.client_id,
client_secret: newClient.client_secret, // 返回客户端密钥
name: newClient.name,
description: newClient.description,
redirect_uris: newClient.redirect_uris,
scopes: newClient.scopes,
created_at: newClient.created_at
}
});
} catch (error) {
console.error('创建OAuth客户端失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
});
// 2. 获取用户的所有OAuth客户端
router.get('/clients', authenticateToken, async (req, res) => {
try {
const userId = req.user.userId;
const clients = await OAuthClient.findByUserId(userId);
res.json({
success: true,
data: {
clients: clients.map(client => ({
client_id: client.client_id,
name: client.name,
description: client.description,
redirect_uris: client.redirect_uris,
scopes: client.scopes,
created_at: client.created_at
}))
}
});
} catch (error) {
console.error('获取OAuth客户端失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
});
// 3. 获取特定OAuth客户端详情
router.get('/clients/:clientId', authenticateToken, async (req, res) => {
try {
const { clientId } = req.params;
const userId = req.user.userId;
const client = await OAuthClient.findByClientId(clientId);
if (!client) {
return res.status(404).json({
success: false,
message: 'OAuth客户端不存在'
});
}
// 检查是否是客户端所有者
if (client.user_id !== userId) {
return res.status(403).json({
success: false,
message: '无权访问此客户端'
});
}
res.json({
success: true,
data: {
client_id: client.client_id,
name: client.name,
description: client.description,
redirect_uris: client.redirect_uris,
scopes: client.scopes,
created_at: client.created_at
}
});
} catch (error) {
console.error('获取OAuth客户端详情失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
});
// 4. 删除OAuth客户端
router.delete('/clients/:clientId', authenticateToken, async (req, res) => {
try {
const { clientId } = req.params;
const userId = req.user.userId;
const deleted = await OAuthClient.delete(clientId, userId);
if (!deleted) {
return res.status(404).json({
success: false,
message: 'OAuth客户端不存在或无权删除'
});
}
res.json({
success: true,
message: 'OAuth客户端已删除'
});
} catch (error) {
console.error('删除OAuth客户端失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
});
// 4. 获取客户端密钥(仅限客户端所有者)
router.get('/clients/:clientId/secret', authenticateToken, async (req, res) => {
try {
const { clientId } = req.params;
const userId = req.user.userId;
const client = await OAuthClient.findByClientId(clientId);
if (!client) {
return res.status(404).json({
success: false,
message: 'OAuth客户端不存在'
});
}
// 检查是否是客户端所有者
if (client.user_id !== userId) {
return res.status(403).json({
success: false,
message: '无权访问此客户端'
});
}
res.json({
success: true,
data: {
client_id: client.client_id,
client_secret: client.client_secret
}
});
} catch (error) {
console.error('获取客户端密钥失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
});
// 5. 重置客户端密钥
router.post('/clients/:clientId/reset-secret', authenticateToken, async (req, res) => {
try {
const { clientId } = req.params;
const userId = req.user.userId;
const client = await OAuthClient.findByClientId(clientId);
if (!client) {
return res.status(404).json({
success: false,
message: 'OAuth客户端不存在'
});
}
// 检查是否是客户端所有者
if (client.user_id !== userId) {
return res.status(403).json({
success: false,
message: '无权操作此客户端'
});
}
const newSecret = await OAuthClient.resetClientSecret(clientId);
res.json({
success: true,
message: '客户端密钥重置成功',
data: {
client_id: clientId,
client_secret: newSecret
}
});
} catch (error) {
console.error('重置客户端密钥失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
});
// 5. 获取OAuth客户端统计信息
router.get('/clients/:clientId/stats', authenticateToken, async (req, res) => {
try {
const { clientId } = req.params;
const userId = req.user.userId;
const client = await OAuthClient.findByClientId(clientId);
if (!client || client.user_id !== userId) {
return res.status(404).json({
success: false,
message: 'OAuth客户端不存在'
});
}
// 这里可以添加更多统计信息,比如活跃令牌数量等
res.json({
success: true,
data: {
client_id: client.client_id,
name: client.name,
created_at: client.created_at,
// 可以添加更多统计信息
}
});
} catch (error) {
console.error('获取OAuth客户端统计失败:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
});
module.exports = router;