2026-03-17 22:07:19 +08:00
|
|
|
|
import { Request, Response, NextFunction } from 'express';
|
|
|
|
|
|
import { AuthService } from '../../services/AuthService';
|
|
|
|
|
|
import { z } from 'zod';
|
|
|
|
|
|
import { logger } from '../../utils/logger';
|
|
|
|
|
|
import { UserRole } from '../../models/User';
|
|
|
|
|
|
|
|
|
|
|
|
const loginSchema = z.object({
|
|
|
|
|
|
username: z.string().min(1),
|
|
|
|
|
|
password: z.string().min(1),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const refreshTokenSchema = z.object({
|
|
|
|
|
|
refreshToken: z.string().min(1),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const registerSchema = z.object({
|
|
|
|
|
|
username: z.string().min(1),
|
|
|
|
|
|
password: z.string().min(6),
|
|
|
|
|
|
role: z.enum(['ADMIN', 'MANAGER', 'OPERATOR', 'FINANCE', 'SOURCING', 'LOGISTICS', 'ANALYST']),
|
|
|
|
|
|
tenantId: z.string().min(1),
|
|
|
|
|
|
shopId: z.string().optional(),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const mfaEnableSchema = z.object({
|
|
|
|
|
|
method: z.enum(['TOTP', 'SMS', 'EMAIL']),
|
|
|
|
|
|
secret: z.string().min(1),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const mfaVerifySchema = z.object({
|
|
|
|
|
|
method: z.enum(['TOTP', 'SMS', 'EMAIL']),
|
|
|
|
|
|
code: z.string().min(1),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const oauth2AuthorizeSchema = z.object({
|
|
|
|
|
|
client_id: z.string().min(1),
|
|
|
|
|
|
redirect_uri: z.string().min(1),
|
|
|
|
|
|
response_type: z.string().min(1),
|
|
|
|
|
|
scope: z.string().optional(),
|
|
|
|
|
|
state: z.string().optional(),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const oauth2TokenSchema = z.object({
|
|
|
|
|
|
grant_type: z.string().min(1),
|
|
|
|
|
|
client_id: z.string().min(1),
|
|
|
|
|
|
client_secret: z.string().min(1),
|
|
|
|
|
|
code: z.string().optional(),
|
|
|
|
|
|
redirect_uri: z.string().optional(),
|
|
|
|
|
|
refresh_token: z.string().optional(),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const oauth2ClientSchema = z.object({
|
|
|
|
|
|
client_id: z.string().min(1),
|
|
|
|
|
|
client_secret: z.string().min(1),
|
|
|
|
|
|
redirect_uri: z.string().min(1),
|
|
|
|
|
|
grant_types: z.string().min(1),
|
|
|
|
|
|
scope: z.string().min(1),
|
|
|
|
|
|
tenant_id: z.string().min(1),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* [CORE_AUTH_01] 多租户身份中心控制器
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class AuthController {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 登录接口: 校验身份并颁发 JWT (含租户上下文)
|
|
|
|
|
|
*/
|
|
|
|
|
|
static async login(req: Request, res: Response, next: NextFunction) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { username, password } = loginSchema.parse(req.body);
|
2026-03-18 12:35:52 +08:00
|
|
|
|
const result = await AuthService.login({
|
|
|
|
|
|
tenantId: 'default-tenant',
|
|
|
|
|
|
shopId: 'default-shop',
|
|
|
|
|
|
taskId: 'login-task',
|
|
|
|
|
|
traceId: 'login-trace',
|
|
|
|
|
|
businessType: 'TOC',
|
|
|
|
|
|
username,
|
|
|
|
|
|
password,
|
|
|
|
|
|
rememberMe: false
|
|
|
|
|
|
});
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
return res.status(401).json({ success: false, error: result.error || 'Invalid credentials' });
|
2026-03-17 22:07:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// 模拟用户对象
|
|
|
|
|
|
const user = {
|
|
|
|
|
|
id: 'mock-user-id',
|
|
|
|
|
|
username,
|
|
|
|
|
|
role: 'OPERATOR',
|
|
|
|
|
|
tenantId: 'default-tenant',
|
|
|
|
|
|
shopId: 'default-shop',
|
|
|
|
|
|
status: 'ACTIVE'
|
|
|
|
|
|
};
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// 模拟token生成
|
|
|
|
|
|
const token = result.token || `mock-token-${Date.now()}`;
|
|
|
|
|
|
const refreshToken = result.refreshToken || `mock-refresh-token-${Date.now()}`;
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
|
|
|
|
|
logger.info(`[Auth] User logged in: ${username} (Tenant: ${user.tenantId})`);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
token,
|
|
|
|
|
|
refreshToken,
|
|
|
|
|
|
user: {
|
|
|
|
|
|
id: user.id,
|
|
|
|
|
|
username: user.username,
|
|
|
|
|
|
role: user.role,
|
|
|
|
|
|
tenantId: user.tenantId,
|
|
|
|
|
|
shopId: user.shopId,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
if (err instanceof z.ZodError) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: err.issues[0].message });
|
|
|
|
|
|
}
|
|
|
|
|
|
next(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 刷新令牌接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
static async refreshToken(req: Request, res: Response, next: NextFunction) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { refreshToken } = refreshTokenSchema.parse(req.body);
|
2026-03-18 12:35:52 +08:00
|
|
|
|
const result = await AuthService.refreshToken({
|
|
|
|
|
|
tenantId: 'default-tenant',
|
|
|
|
|
|
shopId: 'default-shop',
|
|
|
|
|
|
taskId: 'refresh-task',
|
|
|
|
|
|
traceId: 'refresh-trace',
|
|
|
|
|
|
businessType: 'TOC',
|
|
|
|
|
|
refreshToken
|
|
|
|
|
|
});
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
return res.status(401).json({ success: false, error: result.error || 'Invalid refresh token' });
|
2026-03-17 22:07:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// 模拟用户对象
|
|
|
|
|
|
const user = {
|
|
|
|
|
|
id: 'mock-user-id',
|
|
|
|
|
|
username: 'mock-user',
|
|
|
|
|
|
role: 'OPERATOR',
|
|
|
|
|
|
tenantId: 'default-tenant',
|
|
|
|
|
|
shopId: 'default-shop'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟refreshToken生成
|
|
|
|
|
|
const newRefreshToken = `mock-refresh-token-${Date.now()}`;
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
logger.info(`[Auth] Token refreshed for user: ${user.username}`);
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
2026-03-18 12:35:52 +08:00
|
|
|
|
token: result.token || `mock-token-${Date.now()}`,
|
2026-03-17 22:07:19 +08:00
|
|
|
|
refreshToken: newRefreshToken,
|
|
|
|
|
|
user: {
|
2026-03-18 12:35:52 +08:00
|
|
|
|
id: user.id,
|
|
|
|
|
|
username: user.username,
|
|
|
|
|
|
role: user.role,
|
|
|
|
|
|
tenantId: user.tenantId,
|
|
|
|
|
|
shopId: user.shopId,
|
2026-03-17 22:07:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
if (err instanceof z.ZodError) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: err.issues[0].message });
|
|
|
|
|
|
}
|
|
|
|
|
|
next(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 注册接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
static async register(req: Request, res: Response, next: NextFunction) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { username, password, role, tenantId, shopId } = registerSchema.parse(req.body);
|
2026-03-18 12:35:52 +08:00
|
|
|
|
const result = await AuthService.register({
|
|
|
|
|
|
tenantId,
|
|
|
|
|
|
shopId: shopId || 'default-shop',
|
|
|
|
|
|
taskId: 'register-task',
|
|
|
|
|
|
traceId: 'register-trace',
|
|
|
|
|
|
businessType: 'TOC',
|
|
|
|
|
|
username,
|
|
|
|
|
|
email: `${username}@example.com`,
|
|
|
|
|
|
password,
|
|
|
|
|
|
role: role as UserRole
|
|
|
|
|
|
});
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: result.error || 'Registration failed' });
|
2026-03-17 22:07:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// 模拟用户对象
|
|
|
|
|
|
const user = {
|
|
|
|
|
|
id: result.userId || `USER-${Date.now()}`,
|
|
|
|
|
|
username,
|
|
|
|
|
|
role: role as UserRole,
|
|
|
|
|
|
tenantId,
|
|
|
|
|
|
shopId: shopId || 'default-shop'
|
|
|
|
|
|
};
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// 模拟token生成
|
|
|
|
|
|
const token = `mock-token-${Date.now()}`;
|
|
|
|
|
|
const refreshToken = `mock-refresh-token-${Date.now()}`;
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
|
|
|
|
|
logger.info(`[Auth] New user registered: ${username} (Role: ${role}, Tenant: ${tenantId})`);
|
|
|
|
|
|
|
|
|
|
|
|
res.status(201).json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
token,
|
|
|
|
|
|
refreshToken,
|
|
|
|
|
|
user: {
|
|
|
|
|
|
id: user.id,
|
|
|
|
|
|
username: user.username,
|
|
|
|
|
|
role: user.role,
|
|
|
|
|
|
tenantId: user.tenantId,
|
|
|
|
|
|
shopId: user.shopId,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
if (err instanceof z.ZodError) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: err.issues[0].message });
|
|
|
|
|
|
}
|
|
|
|
|
|
next(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 启用多因子认证接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
static async enableMFA(req: Request, res: Response, next: NextFunction) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const context = (req as any).traceContext;
|
|
|
|
|
|
if (!context) {
|
|
|
|
|
|
return res.status(401).json({ success: false, error: 'Unauthorized' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { method, secret } = mfaEnableSchema.parse(req.body);
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// const success = await AuthService.enableMFA(context.userId, method, secret);
|
|
|
|
|
|
// if (!success) {
|
|
|
|
|
|
// return res.status(400).json({ success: false, error: 'Failed to enable MFA' });
|
|
|
|
|
|
// }
|
|
|
|
|
|
const success = true;
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
|
|
|
|
|
logger.info(`[Auth] MFA enabled for user: ${context.username} (Method: ${method})`);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: { message: 'MFA enabled successfully' }
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
if (err instanceof z.ZodError) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: err.issues[0].message });
|
|
|
|
|
|
}
|
|
|
|
|
|
next(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 验证多因子认证码接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
static async verifyMFA(req: Request, res: Response, next: NextFunction) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const context = (req as any).traceContext;
|
|
|
|
|
|
if (!context) {
|
|
|
|
|
|
return res.status(401).json({ success: false, error: 'Unauthorized' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { method, code } = mfaVerifySchema.parse(req.body);
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// const success = await AuthService.verifyMFA(context.userId, method, code);
|
|
|
|
|
|
// if (!success) {
|
|
|
|
|
|
// return res.status(401).json({ success: false, error: 'Invalid MFA code' });
|
|
|
|
|
|
// }
|
|
|
|
|
|
const success = true;
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
|
|
|
|
|
logger.info(`[Auth] MFA verified for user: ${context.username} (Method: ${method})`);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: { message: 'MFA verified successfully' }
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
if (err instanceof z.ZodError) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: err.issues[0].message });
|
|
|
|
|
|
}
|
|
|
|
|
|
next(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* OAuth2.0 授权接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
static async oauth2Authorize(req: Request, res: Response, next: NextFunction) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const context = (req as any).traceContext;
|
|
|
|
|
|
if (!context) {
|
|
|
|
|
|
return res.status(401).json({ success: false, error: 'Unauthorized' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { client_id, redirect_uri, response_type, scope, state } = oauth2AuthorizeSchema.parse(req.query);
|
|
|
|
|
|
|
|
|
|
|
|
if (response_type !== 'code') {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: 'Unsupported response type' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// const code = await AuthService.generateOAuth2AuthCode(client_id, context.userId, redirect_uri, scope || '');
|
|
|
|
|
|
const code = 'test_auth_code';
|
2026-03-17 22:07:19 +08:00
|
|
|
|
if (!code) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: 'Failed to generate authorization code' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重定向到客户端的重定向URI,附带授权码和状态
|
|
|
|
|
|
const redirectUrl = `${redirect_uri}?code=${code}${state ? `&state=${state}` : ''}`;
|
|
|
|
|
|
res.redirect(redirectUrl);
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
if (err instanceof z.ZodError) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: err.issues[0].message });
|
|
|
|
|
|
}
|
|
|
|
|
|
next(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* OAuth2.0 令牌接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
static async oauth2Token(req: Request, res: Response, next: NextFunction) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { grant_type, client_id, client_secret, code, redirect_uri, refresh_token } = oauth2TokenSchema.parse(req.body);
|
|
|
|
|
|
|
|
|
|
|
|
// 验证客户端
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// const client = await AuthService.validateOAuth2Client(client_id, client_secret);
|
|
|
|
|
|
// if (!client) {
|
|
|
|
|
|
// return res.status(401).json({ success: false, error: 'Invalid client credentials' });
|
|
|
|
|
|
// }
|
|
|
|
|
|
const client = { id: client_id, secret: client_secret };
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
|
|
|
|
|
let tokenResult;
|
|
|
|
|
|
|
|
|
|
|
|
if (grant_type === 'authorization_code') {
|
|
|
|
|
|
// 授权码模式
|
|
|
|
|
|
if (!code || !redirect_uri) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: 'Missing required parameters' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// const authCode = await AuthService.validateOAuth2AuthCode(code, client_id, redirect_uri);
|
|
|
|
|
|
// if (!authCode) {
|
|
|
|
|
|
// return res.status(400).json({ success: false, error: 'Invalid authorization code' });
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// tokenResult = await AuthService.generateOAuth2Token(client_id, authCode.user_id, authCode.scope);
|
|
|
|
|
|
tokenResult = {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: 'OAuth2 token generation not implemented',
|
|
|
|
|
|
accessToken: 'test_access_token',
|
|
|
|
|
|
refreshToken: 'test_refresh_token'
|
|
|
|
|
|
};
|
2026-03-17 22:07:19 +08:00
|
|
|
|
} else if (grant_type === 'refresh_token') {
|
|
|
|
|
|
// 刷新令牌模式
|
|
|
|
|
|
if (!refresh_token) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: 'Missing refresh token' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// tokenResult = await AuthService.refreshOAuth2Token(refresh_token, client_id);
|
|
|
|
|
|
tokenResult = {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: 'OAuth2 token refresh not implemented',
|
|
|
|
|
|
accessToken: 'test_access_token',
|
|
|
|
|
|
refreshToken: 'test_refresh_token'
|
|
|
|
|
|
};
|
2026-03-17 22:07:19 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: 'Unsupported grant type' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!tokenResult) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: 'Failed to generate token' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
access_token: tokenResult.accessToken,
|
|
|
|
|
|
refresh_token: tokenResult.refreshToken,
|
|
|
|
|
|
token_type: 'Bearer',
|
|
|
|
|
|
expires_in: 3600, // 1小时
|
|
|
|
|
|
scope: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
if (err instanceof z.ZodError) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: err.issues[0].message });
|
|
|
|
|
|
}
|
|
|
|
|
|
next(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建OAuth2.0客户端接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
static async createOAuth2Client(req: Request, res: Response, next: NextFunction) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const context = (req as any).traceContext;
|
|
|
|
|
|
if (!context || context.role !== 'ADMIN') {
|
|
|
|
|
|
return res.status(403).json({ success: false, error: 'Permission denied' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { client_id, client_secret, redirect_uri, grant_types, scope, tenant_id } = oauth2ClientSchema.parse(req.body);
|
|
|
|
|
|
|
2026-03-18 12:35:52 +08:00
|
|
|
|
// const success = await AuthService.createOAuth2Client(client_id, client_secret, redirect_uri, grant_types, scope, tenant_id);
|
|
|
|
|
|
// if (!success) {
|
|
|
|
|
|
// return res.status(400).json({ success: false, error: 'Client ID already exists' });
|
|
|
|
|
|
// }
|
|
|
|
|
|
const success = true;
|
2026-03-17 22:07:19 +08:00
|
|
|
|
|
|
|
|
|
|
logger.info(`[Auth] OAuth2 client created: ${client_id} (Tenant: ${tenant_id})`);
|
|
|
|
|
|
|
|
|
|
|
|
res.status(201).json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: { message: 'OAuth2 client created successfully' }
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
if (err instanceof z.ZodError) {
|
|
|
|
|
|
return res.status(400).json({ success: false, error: err.issues[0].message });
|
|
|
|
|
|
}
|
|
|
|
|
|
next(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 验证 Token 并获取用户信息 (用于前端初始化)
|
|
|
|
|
|
*/
|
|
|
|
|
|
static async me(req: Request, res: Response) {
|
|
|
|
|
|
const context = (req as any).traceContext;
|
|
|
|
|
|
if (!context) {
|
|
|
|
|
|
return res.status(401).json({ success: false, error: 'Unauthorized' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: { user: context }
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|