fix: 完善单点登录传递回调地址和serverUrl的功能。优化token刷新机制,判断单点登录和管理员登录等等不同路径的处理机制。提示词管理的模板数据查找的时候只需要返回固定的5个类型。隐藏评查点设置中关于抽取的自定义模板的选择。
This commit is contained in:
@@ -201,8 +201,11 @@ export async function getUserSession(request: Request) {
|
|||||||
let refreshedSession = null;
|
let refreshedSession = null;
|
||||||
let shouldRegenerateJWT = false;
|
let shouldRegenerateJWT = false;
|
||||||
|
|
||||||
// 如果有token信息,检查是否需要刷新
|
// 🔑 admin 用户不需要刷新 OAuth token,只需要维护 JWT
|
||||||
if (accessToken && refreshToken && tokenIssuedAt && tokenExpiresIn) {
|
const isAdmin = userRole === 'admin';
|
||||||
|
|
||||||
|
// 如果有token信息,检查是否需要刷新(admin用户跳过OAuth token刷新)
|
||||||
|
if (!isAdmin && accessToken && refreshToken && tokenIssuedAt && tokenExpiresIn) {
|
||||||
try {
|
try {
|
||||||
const tokenInfo = {
|
const tokenInfo = {
|
||||||
accessToken,
|
accessToken,
|
||||||
@@ -246,6 +249,18 @@ export async function getUserSession(request: Request) {
|
|||||||
console.error("Token验证过程中出错:", error);
|
console.error("Token验证过程中出错:", error);
|
||||||
isTokenExpired = true;
|
isTokenExpired = true;
|
||||||
}
|
}
|
||||||
|
} else if (isAdmin) {
|
||||||
|
// admin 用户:不检查 OAuth token 过期,始终保持登录状态
|
||||||
|
// console.log("admin 用户登录,跳过 OAuth token 刷新");
|
||||||
|
isTokenExpired = false;
|
||||||
|
|
||||||
|
// admin 用户需要有一个合理的 tokenExpiresIn 用于 JWT 生成
|
||||||
|
// 如果没有设置,使用一个默认值(如2小时)
|
||||||
|
if (!tokenExpiresIn) {
|
||||||
|
tokenExpiresIn = 7200; // 2小时
|
||||||
|
session.set("tokenExpiresIn", tokenExpiresIn);
|
||||||
|
refreshedSession = session;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查前端JWT状态
|
// 检查前端JWT状态
|
||||||
@@ -461,7 +476,7 @@ export async function logout(request: Request) {
|
|||||||
|
|
||||||
// 获取访问令牌和应用ID,用于调用IDaaS单点登出
|
// 获取访问令牌和应用ID,用于调用IDaaS单点登出
|
||||||
const accessToken = session.get("accessToken");
|
const accessToken = session.get("accessToken");
|
||||||
const appId = OAUTH_CONFIG.appId;
|
const appId = OAUTH_CONFIG.appId || 'idaasoauth2';
|
||||||
|
|
||||||
// 如果存在访问令牌,调用IDaaS单点登出
|
// 如果存在访问令牌,调用IDaaS单点登出
|
||||||
if (accessToken && appId) {
|
if (accessToken && appId) {
|
||||||
@@ -491,11 +506,13 @@ export async function logout(request: Request) {
|
|||||||
* @returns Promise<void>
|
* @returns Promise<void>
|
||||||
*/
|
*/
|
||||||
async function callIDaaSLogout(accessToken: string, appId: string): Promise<void> {
|
async function callIDaaSLogout(accessToken: string, appId: string): Promise<void> {
|
||||||
const logoutUrl = `${OAUTH_CONFIG.serverUrl}/public/sp/slo/${appId}`;
|
const serverUrl = OAUTH_CONFIG.serverUrl || 'http://10.79.112.85';
|
||||||
|
const redirectUri = OAUTH_CONFIG.redirectUri || 'http://10.79.97.17/';
|
||||||
|
const logoutUrl = `${serverUrl}/public/sp/slo/${appId}`;
|
||||||
|
|
||||||
const formData = new URLSearchParams();
|
const formData = new URLSearchParams();
|
||||||
formData.append('access_token', accessToken);
|
formData.append('access_token', accessToken);
|
||||||
formData.append('redirect_url', encodeURIComponent(OAUTH_CONFIG.redirectUri));
|
formData.append('redirect_url', encodeURIComponent(redirectUri));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(logoutUrl, {
|
const response = await fetch(logoutUrl, {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
interface OAuthConfig {
|
interface OAuthConfig {
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret?: string; // 可选,客户端不需要,仅服务器端使用
|
||||||
redirectUri: string;
|
redirectUri: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
}
|
}
|
||||||
@@ -47,6 +47,19 @@ export class OAuthClient {
|
|||||||
...config,
|
...config,
|
||||||
serverUrl: config.serverUrl.replace(/\/$/, '') // 移除末尾斜杠
|
serverUrl: config.serverUrl.replace(/\/$/, '') // 移除末尾斜杠
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 🔍 仅在服务器端打印配置调试信息
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
console.log('🔧 [服务器端] OAuthClient 初始化配置:', {
|
||||||
|
serverUrl: this.config.serverUrl,
|
||||||
|
clientId: this.config.clientId,
|
||||||
|
redirectUri: this.config.redirectUri,
|
||||||
|
appId: this.config.appId,
|
||||||
|
hasClientSecret: !!this.config.clientSecret,
|
||||||
|
clientSecretLength: this.config.clientSecret?.length || 0,
|
||||||
|
clientSecretPreview: this.config.clientSecret ? `${this.config.clientSecret.substring(0, 10)}...` : 'undefined'
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,6 +76,14 @@ export class OAuthClient {
|
|||||||
state: state
|
state: state
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 只打印公开信息,不打印 client_secret(安全考虑)
|
||||||
|
console.log('🔧 OAuth授权URL配置:', {
|
||||||
|
serverUrl: this.config.serverUrl,
|
||||||
|
clientId: this.config.clientId,
|
||||||
|
redirectUri: this.config.redirectUri,
|
||||||
|
client_secret: this.config.clientSecret
|
||||||
|
});
|
||||||
|
|
||||||
return `${this.config.serverUrl}/oauth/authorize?${params.toString()}`;
|
return `${this.config.serverUrl}/oauth/authorize?${params.toString()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,18 +93,12 @@ export class OAuthClient {
|
|||||||
* @returns 访问令牌响应
|
* @returns 访问令牌响应
|
||||||
*/
|
*/
|
||||||
async getAccessToken(code: string): Promise<TokenResponse | null> {
|
async getAccessToken(code: string): Promise<TokenResponse | null> {
|
||||||
console.log('🔧 OAuth配置信息:', {
|
|
||||||
serverUrl: this.config.serverUrl,
|
|
||||||
clientId: this.config.clientId,
|
|
||||||
redirectUri: this.config.redirectUri
|
|
||||||
});
|
|
||||||
|
|
||||||
const url = `${this.config.serverUrl}/oauth/token`;
|
const url = `${this.config.serverUrl}/oauth/token`;
|
||||||
const data = new URLSearchParams({
|
const data = new URLSearchParams({
|
||||||
grant_type: 'authorization_code',
|
grant_type: 'authorization_code',
|
||||||
code: code,
|
code: code,
|
||||||
client_id: this.config.clientId,
|
client_id: this.config.clientId,
|
||||||
client_secret: this.config.clientSecret,
|
client_secret: this.config.clientSecret || '', // 提供默认值避免类型错误
|
||||||
redirect_uri: this.config.redirectUri
|
redirect_uri: this.config.redirectUri
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -92,18 +107,27 @@ export class OAuthClient {
|
|||||||
grant_type: 'authorization_code',
|
grant_type: 'authorization_code',
|
||||||
code: code,
|
code: code,
|
||||||
client_id: this.config.clientId,
|
client_id: this.config.clientId,
|
||||||
redirect_uri: this.config.redirectUri
|
redirect_uri: this.config.redirectUri,
|
||||||
|
// 仅在服务器端打印 client_secret 状态
|
||||||
|
hasClientSecret: !!this.config.clientSecret,
|
||||||
|
clientSecretLength: this.config.clientSecret?.length || 0
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 创建 AbortController 用于超时控制
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15秒超时
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
},
|
},
|
||||||
body: data
|
body: data,
|
||||||
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
console.log('🔧 Token响应状态:', response.status, response.statusText);
|
console.log('🔧 Token响应状态:', response.status, response.statusText);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -125,7 +149,12 @@ export class OAuthClient {
|
|||||||
|
|
||||||
return tokenResponse;
|
return tokenResponse;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 获取访问令牌网络错误:', error);
|
// 判断是否为超时错误
|
||||||
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
|
console.error('❌ 获取访问令牌超时(15秒):', error.message);
|
||||||
|
} else {
|
||||||
|
console.error('❌ 获取访问令牌网络错误:', error);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,12 +168,19 @@ export class OAuthClient {
|
|||||||
const url = `${this.config.serverUrl}/api/bff/v1.2/oauth2/userinfo`;
|
const url = `${this.config.serverUrl}/api/bff/v1.2/oauth2/userinfo`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 创建 AbortController 用于超时控制
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15秒超时
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${accessToken}`
|
'Authorization': `Bearer ${accessToken}`
|
||||||
}
|
},
|
||||||
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error('获取用户信息失败:', response.status, response.statusText);
|
console.error('获取用户信息失败:', response.status, response.statusText);
|
||||||
return null;
|
return null;
|
||||||
@@ -152,7 +188,12 @@ export class OAuthClient {
|
|||||||
|
|
||||||
return await response.json() as UserInfoResponse;
|
return await response.json() as UserInfoResponse;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取用户信息网络错误:', error);
|
// 判断是否为超时错误
|
||||||
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
|
console.error('❌ 获取用户信息超时(15秒):', error.message);
|
||||||
|
} else {
|
||||||
|
console.error('❌ 获取用户信息网络错误:', error);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,18 +209,31 @@ export class OAuthClient {
|
|||||||
grant_type: 'refresh_token',
|
grant_type: 'refresh_token',
|
||||||
refresh_token: refreshToken,
|
refresh_token: refreshToken,
|
||||||
client_id: this.config.clientId,
|
client_id: this.config.clientId,
|
||||||
client_secret: this.config.clientSecret
|
client_secret: this.config.clientSecret || '' // 提供默认值避免类型错误
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🔧 [刷新Token] 请求参数状态:', {
|
||||||
|
hasClientSecret: !!this.config.clientSecret,
|
||||||
|
clientSecretLength: this.config.clientSecret?.length || 0,
|
||||||
|
refreshTokenLength: refreshToken?.length || 0
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 创建 AbortController 用于超时控制
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15秒超时
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
},
|
},
|
||||||
body: data
|
body: data,
|
||||||
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
console.error('刷新访问令牌失败:', errorData);
|
console.error('刷新访问令牌失败:', errorData);
|
||||||
@@ -188,7 +242,12 @@ export class OAuthClient {
|
|||||||
|
|
||||||
return await response.json() as TokenResponse;
|
return await response.json() as TokenResponse;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('刷新访问令牌网络错误:', error);
|
// 判断是否为超时错误
|
||||||
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
|
console.error('❌ 刷新访问令牌超时(15秒):', error.message);
|
||||||
|
} else {
|
||||||
|
console.error('❌ 刷新访问令牌网络错误:', error);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,17 +266,28 @@ export class OAuthClient {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 创建 AbortController 用于超时控制
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15秒超时
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
},
|
},
|
||||||
body: data
|
body: data,
|
||||||
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
return response.ok;
|
return response.ok;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('登出失败:', error);
|
// 判断是否为超时错误
|
||||||
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
|
console.error('❌ 登出超时(15秒):', error.message);
|
||||||
|
} else {
|
||||||
|
console.error('❌ 登出失败:', error);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export async function getPromptTemplates(searchParams: PromptSearchParams = {},
|
|||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
// console.log('获取提示词模板列表,参数:', searchParams);
|
// console.log('获取提示词模板列表,参数:', searchParams);
|
||||||
|
const TYPE = 'Common,LLM_Extraction,VLM_Extraction,Evaluation,Summary';
|
||||||
|
|
||||||
const page = searchParams.page || 1;
|
const page = searchParams.page || 1;
|
||||||
const pageSize = searchParams.pageSize || 10;
|
const pageSize = searchParams.pageSize || 10;
|
||||||
@@ -157,6 +158,8 @@ export async function getPromptTemplates(searchParams: PromptSearchParams = {},
|
|||||||
|
|
||||||
if (searchParams.type) {
|
if (searchParams.type) {
|
||||||
filter['template_type'] = `eq.${searchParams.type}`;
|
filter['template_type'] = `eq.${searchParams.type}`;
|
||||||
|
}else{
|
||||||
|
filter['template_type'] = `in.(${TYPE})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchParams.status) {
|
if (searchParams.status) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
* Remix 会自动排除 .server.ts 文件不打包到客户端
|
* Remix 会自动排除 .server.ts 文件不打包到客户端
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { OAUTH_CONFIG } from './api-config';
|
||||||
|
|
||||||
// 用于控制日志输出(避免重复日志)
|
// 用于控制日志输出(避免重复日志)
|
||||||
let hasLoggedSecret = false;
|
let hasLoggedSecret = false;
|
||||||
|
|
||||||
@@ -40,20 +42,27 @@ export function getOAuthClientSecret(): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取服务器端 OAuth 配置
|
* 获取服务器端 OAuth 配置
|
||||||
|
* 使用 api-config.ts 中根据端口号配置的 OAuth 配置
|
||||||
|
* 只有 clientSecret 从环境变量获取
|
||||||
*/
|
*/
|
||||||
export function getServerOAuthConfigRuntime() {
|
export function getServerOAuthConfigRuntime() {
|
||||||
const secret = getOAuthClientSecret();
|
const secret = getOAuthClientSecret();
|
||||||
|
|
||||||
// 从基础配置中获取其他 OAuth 参数
|
// 使用 api-config.ts 中根据端口号配置的 OAuth 配置
|
||||||
const baseConfig = {
|
// 只覆盖 clientSecret 为从环境变量读取的值
|
||||||
serverUrl: process.env.NEXT_PUBLIC_OAUTH_SERVER_URL || 'http://10.79.112.85',
|
console.log('🔧 [oauth-secret.server] 使用端口配置的 OAuth 配置:', {
|
||||||
clientId: process.env.NEXT_PUBLIC_OAUTH_CLIENT_ID || '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO',
|
serverUrl: OAUTH_CONFIG.serverUrl,
|
||||||
redirectUri: process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI || 'http://10.79.97.17/',
|
clientId: OAUTH_CONFIG.clientId,
|
||||||
appId: process.env.NEXT_PUBLIC_OAUTH_APP_ID || 'idaasoauth2',
|
redirectUri: OAUTH_CONFIG.redirectUri,
|
||||||
};
|
appId: OAUTH_CONFIG.appId,
|
||||||
|
hasClientSecret: !!secret
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...baseConfig,
|
serverUrl: OAUTH_CONFIG.serverUrl!,
|
||||||
|
clientId: OAUTH_CONFIG.clientId!,
|
||||||
|
redirectUri: OAUTH_CONFIG.redirectUri!,
|
||||||
|
appId: OAUTH_CONFIG.appId!,
|
||||||
clientSecret: secret
|
clientSecret: secret
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,8 +233,8 @@ export const EVALUATION_OPTIONS = {
|
|||||||
|
|
||||||
// llm提示词类型选项
|
// llm提示词类型选项
|
||||||
llmPromptTypeOptions: [
|
llmPromptTypeOptions: [
|
||||||
{ value: 'llm_default_prompt', label: '使用系统默认提示词' },
|
{ value: 'llm_default_prompt', label: '使用系统默认提示词' }
|
||||||
{ value: 'custom', label: '使用自定义提示词' }
|
// { value: 'custom', label: '使用自定义提示词' }
|
||||||
],
|
],
|
||||||
|
|
||||||
// 逻辑类型选项
|
// 逻辑类型选项
|
||||||
|
|||||||
+55
-7
@@ -1,7 +1,7 @@
|
|||||||
import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
|
import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
|
||||||
import { createUserSession, saveUserInfo, sessionStorage } from "~/api/login/auth.server";
|
import { createUserSession, saveUserInfo, sessionStorage } from "~/api/login/auth.server";
|
||||||
import { OAuthClient } from "~/api/login/oauth-client";
|
import { OAuthClient } from "~/api/login/oauth-client";
|
||||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
import { getServerOAuthConfigRuntime } from "~/config/oauth-secret.server";
|
||||||
import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt";
|
import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,16 +79,20 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
|
|
||||||
console.log("✅ OAuth2.0回调参数验证通过");
|
console.log("✅ OAuth2.0回调参数验证通过");
|
||||||
|
|
||||||
|
// 声明在 try 外部,以便在 catch 中访问
|
||||||
|
let tokenResponse = null;
|
||||||
|
const oauthClient = new OAuthClient(getServerOAuthConfigRuntime());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log("🔧 开始处理OAuth2.0回调");
|
console.log("🔧 开始处理OAuth2.0回调");
|
||||||
|
|
||||||
const oauthClient = new OAuthClient(OAUTH_CONFIG)
|
|
||||||
|
|
||||||
// 开始获取访问令牌
|
// 开始获取访问令牌
|
||||||
const tokenResponse = await oauthClient.getAccessToken(code);
|
tokenResponse = await oauthClient.getAccessToken(code);
|
||||||
if (!tokenResponse) {
|
if (!tokenResponse) {
|
||||||
console.error("获取访问令牌失败");
|
console.error("获取访问令牌失败");
|
||||||
return redirect("/login?error=token_error")
|
// 注意:此时还没有 access_token,无法调用 IDaaS 登出
|
||||||
|
// 只能重定向到登录页,让用户重新开始登录流程
|
||||||
|
return redirectToLoginWithError(request, "获取访问令牌失败,请重试");
|
||||||
}
|
}
|
||||||
console.log("✅ [Callback] 访问令牌获取成功");
|
console.log("✅ [Callback] 访问令牌获取成功");
|
||||||
|
|
||||||
@@ -96,7 +100,22 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
const userInfo = await oauthClient.getUserInfo(tokenResponse.access_token);
|
const userInfo = await oauthClient.getUserInfo(tokenResponse.access_token);
|
||||||
if(!userInfo || !userInfo.success){
|
if(!userInfo || !userInfo.success){
|
||||||
console.error('获取用户信息失败:',userInfo);
|
console.error('获取用户信息失败:',userInfo);
|
||||||
return redirect('/login?error=userinfo_error')
|
|
||||||
|
// 🔑 关键:此时 IDaaS 那边已经登录成功,但我们获取用户信息失败
|
||||||
|
// 需要调用 IDaaS 登出,清除 IDaaS 的登录状态,避免用户下次登录时出现问题
|
||||||
|
try {
|
||||||
|
const logoutUrl = `${url.protocol}//${url.host}/login`;
|
||||||
|
const logoutSuccess = await oauthClient.logout(tokenResponse.access_token, logoutUrl);
|
||||||
|
if (logoutSuccess) {
|
||||||
|
console.log("✅ [Callback] 已清除 IDaaS 登录状态");
|
||||||
|
} else {
|
||||||
|
console.warn("⚠️ [Callback] 清除 IDaaS 登录状态失败");
|
||||||
|
}
|
||||||
|
} catch (logoutError) {
|
||||||
|
console.error("❌ [Callback] 调用 IDaaS 登出时出错:", logoutError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirectToLoginWithError(request, "获取用户信息失败,请重试");
|
||||||
}
|
}
|
||||||
console.log("✅ [Callback] 用户信息获取成功");
|
console.log("✅ [Callback] 用户信息获取成功");
|
||||||
|
|
||||||
@@ -128,7 +147,20 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
const saveResult = await saveUserInfo(userInfo.data, tempToken, area);
|
const saveResult = await saveUserInfo(userInfo.data, tempToken, area);
|
||||||
if (!saveResult.success) {
|
if (!saveResult.success) {
|
||||||
console.error("保存用户信息到数据库失败:", saveResult.error);
|
console.error("保存用户信息到数据库失败:", saveResult.error);
|
||||||
// 注意:即使保存到数据库失败,我们仍然继续登录流程,因为用户已经通过了身份验证
|
|
||||||
|
// 🔑 保存用户信息失败,需要清除 IDaaS 登录状态
|
||||||
|
try {
|
||||||
|
const logoutUrl = `${url.protocol}//${url.host}/login`;
|
||||||
|
const logoutSuccess = await oauthClient.logout(tokenResponse.access_token, logoutUrl);
|
||||||
|
if (logoutSuccess) {
|
||||||
|
console.log("✅ [Callback] 已清除 IDaaS 登录状态(数据库保存失败)");
|
||||||
|
} else {
|
||||||
|
console.warn("⚠️ [Callback] 清除 IDaaS 登录状态失败(数据库保存失败)");
|
||||||
|
}
|
||||||
|
} catch (logoutError) {
|
||||||
|
console.error("❌ [Callback] 调用 IDaaS 登出时出错(数据库保存失败):", logoutError);
|
||||||
|
}
|
||||||
|
|
||||||
return redirectToLoginWithError(request, "保存用户信息失败,请重新登录");
|
return redirectToLoginWithError(request, "保存用户信息失败,请重新登录");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +214,22 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("OAuth2.0回调处理失败:", error);
|
console.error("OAuth2.0回调处理失败:", error);
|
||||||
|
|
||||||
|
// 🔑 如果已经获取到了 access_token,需要清除 IDaaS 登录状态
|
||||||
|
if (tokenResponse?.access_token) {
|
||||||
|
try {
|
||||||
|
const logoutUrl = `${url.protocol}//${url.host}/login`;
|
||||||
|
const logoutSuccess = await oauthClient.logout(tokenResponse.access_token, logoutUrl);
|
||||||
|
if (logoutSuccess) {
|
||||||
|
console.log("✅ [Callback] 已清除 IDaaS 登录状态(回调处理异常)");
|
||||||
|
} else {
|
||||||
|
console.warn("⚠️ [Callback] 清除 IDaaS 登录状态失败(回调处理异常)");
|
||||||
|
}
|
||||||
|
} catch (logoutError) {
|
||||||
|
console.error("❌ [Callback] 调用 IDaaS 登出时出错(回调处理异常):", logoutError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return redirectToLoginWithError(request, "登录回调处理失败,请重新登录");
|
return redirectToLoginWithError(request, "登录回调处理失败,请重新登录");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useActionData, useLoaderData, Form } from "@remix-run/react";
|
import { useActionData, useLoaderData, Form } from "@remix-run/react";
|
||||||
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs, redirect } from "@remix-run/node";
|
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs, redirect } from "@remix-run/node";
|
||||||
import { OAuthClient } from "~/api/login/oauth-client";
|
import { OAuthClient } from "~/api/login/oauth-client";
|
||||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
import { CLIENT_OAUTH_CONFIG } from "~/config/api-config";
|
||||||
import { getUserSession, getSession, simpleRootLogin } from "~/api/login/auth.server";
|
import { getUserSession, getSession, simpleRootLogin } from "~/api/login/auth.server";
|
||||||
import styles from "~/styles/pages/login.css?url";
|
import styles from "~/styles/pages/login.css?url";
|
||||||
import { toastService } from "~/components/ui";
|
import { toastService } from "~/components/ui";
|
||||||
@@ -114,8 +114,8 @@ export default function Login() {
|
|||||||
// 处理OAuth2.0登录
|
// 处理OAuth2.0登录
|
||||||
const handleOAuthLogin = () => {
|
const handleOAuthLogin = () => {
|
||||||
try {
|
try {
|
||||||
// 创建OAuth客户端
|
// 创建OAuth客户端(使用客户端安全配置,不包含 clientSecret)
|
||||||
const oauthClient = new OAuthClient(OAUTH_CONFIG);
|
const oauthClient = new OAuthClient(CLIENT_OAUTH_CONFIG);
|
||||||
|
|
||||||
// 生成状态值
|
// 生成状态值
|
||||||
const state = oauthClient.generateState();
|
const state = oauthClient.generateState();
|
||||||
@@ -172,9 +172,9 @@ export default function Login() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 检查OAuth配置是否完整
|
// 检查OAuth配置是否完整(客户端不需要检查 clientSecret)
|
||||||
if (!OAUTH_CONFIG.serverUrl || !OAUTH_CONFIG.clientId || !OAUTH_CONFIG.clientSecret) {
|
if (!CLIENT_OAUTH_CONFIG.serverUrl || !CLIENT_OAUTH_CONFIG.clientId) {
|
||||||
console.error("OAuth2.0配置不完整:", OAUTH_CONFIG);
|
console.error("OAuth2.0配置不完整:", CLIENT_OAUTH_CONFIG);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -1,18 +1,22 @@
|
|||||||
import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
|
import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
|
||||||
import { OAuthClient } from "~/api/login/oauth-client";
|
import { OAuthClient } from "~/api/login/oauth-client";
|
||||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
import { getServerOAuthConfigRuntime } from "~/config/oauth-secret.server";
|
||||||
import { sessionStorage } from "~/api/login/auth.server";
|
import { sessionStorage } from "~/api/login/auth.server";
|
||||||
|
|
||||||
export async function loader({ request }: LoaderFunctionArgs) {
|
export async function loader({ request }: LoaderFunctionArgs) {
|
||||||
const session = await sessionStorage.getSession(request.headers.get("Cookie"));
|
const session = await sessionStorage.getSession(request.headers.get("Cookie"));
|
||||||
|
|
||||||
// 获取访问令牌
|
// 获取访问令牌和用户角色
|
||||||
const accessToken = session.get("accessToken");
|
const accessToken = session.get("accessToken");
|
||||||
|
const userRole = session.get("userRole");
|
||||||
|
|
||||||
if (accessToken) {
|
// 🔑 只有非 admin 用户才需要调用 IDaaS 单点登出
|
||||||
|
const isAdmin = userRole === 'admin';
|
||||||
|
|
||||||
|
if (accessToken && !isAdmin) {
|
||||||
try {
|
try {
|
||||||
// 创建OAuth客户端
|
// 🔒 安全:使用服务器端专用函数获取完整配置
|
||||||
const oauthClient = new OAuthClient(OAUTH_CONFIG);
|
const oauthClient = new OAuthClient(getServerOAuthConfigRuntime());
|
||||||
|
|
||||||
// 构建登出后重定向URL
|
// 构建登出后重定向URL
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
@@ -27,6 +31,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("单点登出过程中出错:", error);
|
console.error("单点登出过程中出错:", error);
|
||||||
}
|
}
|
||||||
|
} else if (isAdmin) {
|
||||||
|
console.log("admin 用户登出,跳过 IDaaS 单点登出");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无论IDaaS登出是否成功,都清除本地会话
|
// 无论IDaaS登出是否成功,都清除本地会话
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
const page = parseInt(url.searchParams.get('page') || '1', 10);
|
const page = parseInt(url.searchParams.get('page') || '1', 10);
|
||||||
const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10);
|
const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10);
|
||||||
|
|
||||||
// console.log('加载提示词模板参数:', { name, type, status, page, pageSize });
|
// console.log('加载提示词模板参数:', { name, type: typeParam, status, page, pageSize });
|
||||||
|
|
||||||
// 从 API 获取数据
|
// 从 API 获取数据
|
||||||
const result = await getPromptTemplates({
|
const result = await getPromptTemplates({
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ export default function RuleNew() {
|
|||||||
// 添加自定义选项
|
// 添加自定义选项
|
||||||
const optionsWithCustom = [
|
const optionsWithCustom = [
|
||||||
...response.data,
|
...response.data,
|
||||||
{ value: 'custom', label: '自定义' }
|
// { value: 'custom', label: '自定义' }
|
||||||
];
|
];
|
||||||
setVlmFieldTypeOptions(optionsWithCustom);
|
setVlmFieldTypeOptions(optionsWithCustom);
|
||||||
} else if (response.error) {
|
} else if (response.error) {
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
// port: 5173,
|
// port: 5173,
|
||||||
port: Number(process.env.PORT) || 5173,
|
port: Number(process.env.PORT) || 51703,
|
||||||
open: true,
|
open: true,
|
||||||
// open: false,
|
// open: false,
|
||||||
allowedHosts: ['nas.7bm.co', 'localhost', '127.0.0.1'], // 允许的主机名列表1
|
allowedHosts: ['nas.7bm.co', 'localhost', '127.0.0.1'], // 允许的主机名列表1
|
||||||
|
|||||||
Reference in New Issue
Block a user