From bede408e41332dcede7b02f15e101b05049eb101 Mon Sep 17 00:00:00 2001 From: Gavan <994259213@qq.com> Date: Mon, 14 Jul 2025 14:04:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20oauth=20=E6=B7=BB=E5=8A=A0=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/api/Admin.ts | 232 ++++++++++++++ ui/src/api/User.ts | 215 ------------- ui/src/api/index.ts | 1 + ui/src/api/types.ts | 80 ++--- ui/src/pages/admin/adminTable.tsx | 4 +- ui/src/pages/admin/loginHistory.tsx | 2 +- ui/src/pages/auth/index.tsx | 19 +- ui/src/pages/invite/index.tsx | 22 +- ui/src/pages/login/index.tsx | 2 +- ui/src/pages/user-management/index.tsx | 25 +- .../thirdPartyLoginSettingModal.tsx | 283 +++++++++++++++--- ui/src/pages/user/dashboard/index.tsx | 7 +- ui/src/pages/user/login/index.tsx | 19 +- 13 files changed, 564 insertions(+), 347 deletions(-) create mode 100644 ui/src/api/Admin.ts diff --git a/ui/src/api/Admin.ts b/ui/src/api/Admin.ts new file mode 100644 index 0000000..4eaed3c --- /dev/null +++ b/ui/src/api/Admin.ts @@ -0,0 +1,232 @@ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +import request, { ContentType, RequestParams } from "./httpClient"; +import { + DeleteDeleteAdminParams, + DomainAdminUser, + DomainCreateAdminReq, + DomainListAdminLoginHistoryResp, + DomainListAdminUserResp, + DomainLoginReq, + DomainSetting, + DomainUpdateSettingReq, + GetAdminLoginHistoryParams, + GetListAdminUserParams, + WebResp, +} from "./types"; + +/** + * @description 创建管理员 + * + * @tags Admin + * @name PostCreateAdmin + * @summary 创建管理员 + * @request POST:/api/v1/admin/create + * @response `200` `(WebResp & { + data?: DomainAdminUser, + +})` OK + */ + +export const postCreateAdmin = ( + param: DomainCreateAdminReq, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainAdminUser; + } + >({ + path: `/api/v1/admin/create`, + method: "POST", + body: param, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 删除管理员 + * + * @tags Admin + * @name DeleteDeleteAdmin + * @summary 删除管理员 + * @request DELETE:/api/v1/admin/delete + * @response `200` `(WebResp & { + data?: Record, + +})` OK + */ + +export const deleteDeleteAdmin = ( + query: DeleteDeleteAdminParams, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: Record; + } + >({ + path: `/api/v1/admin/delete`, + method: "DELETE", + query: query, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 获取管理员用户列表 + * + * @tags Admin + * @name GetListAdminUser + * @summary 获取管理员用户列表 + * @request GET:/api/v1/admin/list + * @response `200` `(WebResp & { + data?: DomainListAdminUserResp, + +})` OK + */ + +export const getListAdminUser = ( + query: GetListAdminUserParams, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainListAdminUserResp; + } + >({ + path: `/api/v1/admin/list`, + method: "GET", + query: query, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 管理员登录 + * + * @tags Admin + * @name PostAdminLogin + * @summary 管理员登录 + * @request POST:/api/v1/admin/login + * @response `200` `(WebResp & { + data?: DomainAdminUser, + +})` OK + */ + +export const postAdminLogin = ( + param: DomainLoginReq, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainAdminUser; + } + >({ + path: `/api/v1/admin/login`, + method: "POST", + body: param, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 获取管理员登录历史 + * + * @tags Admin + * @name GetAdminLoginHistory + * @summary 获取管理员登录历史 + * @request GET:/api/v1/admin/login-history + * @response `200` `(WebResp & { + data?: DomainListAdminLoginHistoryResp, + +})` OK + */ + +export const getAdminLoginHistory = ( + query: GetAdminLoginHistoryParams, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainListAdminLoginHistoryResp; + } + >({ + path: `/api/v1/admin/login-history`, + method: "GET", + query: query, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 获取系统设置 + * + * @tags Admin + * @name GetGetSetting + * @summary 获取系统设置 + * @request GET:/api/v1/admin/setting + * @response `200` `(WebResp & { + data?: DomainSetting, + +})` OK + */ + +export const getGetSetting = (params: RequestParams = {}) => + request< + WebResp & { + data?: DomainSetting; + } + >({ + path: `/api/v1/admin/setting`, + method: "GET", + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 更新系统设置 + * + * @tags Admin + * @name PutUpdateSetting + * @summary 更新系统设置 + * @request PUT:/api/v1/admin/setting + * @response `200` `(WebResp & { + data?: DomainSetting, + +})` OK + */ + +export const putUpdateSetting = ( + param: DomainUpdateSettingReq, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainSetting; + } + >({ + path: `/api/v1/admin/setting`, + method: "PUT", + body: param, + type: ContentType.Json, + format: "json", + ...params, + }); diff --git a/ui/src/api/User.ts b/ui/src/api/User.ts index 8646910..7ebbd1a 100644 --- a/ui/src/api/User.ts +++ b/ui/src/api/User.ts @@ -12,25 +12,16 @@ import request, { ContentType, RequestParams } from "./httpClient"; import { - DeleteDeleteAdminParams, DeleteDeleteUserParams, - DomainAdminUser, - DomainCreateAdminReq, DomainInviteResp, - DomainListAdminLoginHistoryResp, - DomainListAdminUserResp, DomainListLoginHistoryResp, DomainListUserResp, DomainLoginReq, DomainLoginResp, DomainOAuthURLResp, DomainRegisterReq, - DomainSetting, - DomainUpdateSettingReq, DomainUpdateUserReq, DomainUser, - GetAdminLoginHistoryParams, - GetListAdminUserParams, GetListUserParams, GetLoginHistoryParams, GetUserOauthCallbackParams, @@ -38,212 +29,6 @@ import { WebResp, } from "./types"; -/** - * @description 创建管理员 - * - * @tags User - * @name PostCreateAdmin - * @summary 创建管理员 - * @request POST:/api/v1/admin/create - * @response `200` `(WebResp & { - data?: DomainAdminUser, - -})` OK - */ - -export const postCreateAdmin = ( - param: DomainCreateAdminReq, - params: RequestParams = {}, -) => - request< - WebResp & { - data?: DomainAdminUser; - } - >({ - path: `/api/v1/admin/create`, - method: "POST", - body: param, - type: ContentType.Json, - format: "json", - ...params, - }); - -/** - * @description 删除管理员 - * - * @tags User - * @name DeleteDeleteAdmin - * @summary 删除管理员 - * @request DELETE:/api/v1/admin/delete - * @response `200` `(WebResp & { - data?: Record, - -})` OK - */ - -export const deleteDeleteAdmin = ( - query: DeleteDeleteAdminParams, - params: RequestParams = {}, -) => - request< - WebResp & { - data?: Record; - } - >({ - path: `/api/v1/admin/delete`, - method: "DELETE", - query: query, - type: ContentType.Json, - format: "json", - ...params, - }); - -/** - * @description 获取管理员用户列表 - * - * @tags User - * @name GetListAdminUser - * @summary 获取管理员用户列表 - * @request GET:/api/v1/admin/list - * @response `200` `(WebResp & { - data?: DomainListAdminUserResp, - -})` OK - */ - -export const getListAdminUser = ( - query: GetListAdminUserParams, - params: RequestParams = {}, -) => - request< - WebResp & { - data?: DomainListAdminUserResp; - } - >({ - path: `/api/v1/admin/list`, - method: "GET", - query: query, - type: ContentType.Json, - format: "json", - ...params, - }); - -/** - * @description 管理员登录 - * - * @tags User - * @name PostAdminLogin - * @summary 管理员登录 - * @request POST:/api/v1/admin/login - * @response `200` `(WebResp & { - data?: DomainAdminUser, - -})` OK - */ - -export const postAdminLogin = ( - param: DomainLoginReq, - params: RequestParams = {}, -) => - request< - WebResp & { - data?: DomainAdminUser; - } - >({ - path: `/api/v1/admin/login`, - method: "POST", - body: param, - type: ContentType.Json, - format: "json", - ...params, - }); - -/** - * @description 获取管理员登录历史 - * - * @tags User - * @name GetAdminLoginHistory - * @summary 获取管理员登录历史 - * @request GET:/api/v1/admin/login-history - * @response `200` `(WebResp & { - data?: DomainListAdminLoginHistoryResp, - -})` OK - */ - -export const getAdminLoginHistory = ( - query: GetAdminLoginHistoryParams, - params: RequestParams = {}, -) => - request< - WebResp & { - data?: DomainListAdminLoginHistoryResp; - } - >({ - path: `/api/v1/admin/login-history`, - method: "GET", - query: query, - type: ContentType.Json, - format: "json", - ...params, - }); - -/** - * @description 获取系统设置 - * - * @tags User - * @name GetGetSetting - * @summary 获取系统设置 - * @request GET:/api/v1/admin/setting - * @response `200` `(WebResp & { - data?: DomainSetting, - -})` OK - */ - -export const getGetSetting = (params: RequestParams = {}) => - request< - WebResp & { - data?: DomainSetting; - } - >({ - path: `/api/v1/admin/setting`, - method: "GET", - type: ContentType.Json, - format: "json", - ...params, - }); - -/** - * @description 更新系统设置 - * - * @tags User - * @name PutUpdateSetting - * @summary 更新系统设置 - * @request PUT:/api/v1/admin/setting - * @response `200` `(WebResp & { - data?: DomainSetting, - -})` OK - */ - -export const putUpdateSetting = ( - param: DomainUpdateSettingReq, - params: RequestParams = {}, -) => - request< - WebResp & { - data?: DomainSetting; - } - >({ - path: `/api/v1/admin/setting`, - method: "PUT", - body: param, - type: ContentType.Json, - format: "json", - ...params, - }); - /** * @description 下载VSCode插件 * diff --git a/ui/src/api/index.ts b/ui/src/api/index.ts index 482f2a8..c9ce7b6 100644 --- a/ui/src/api/index.ts +++ b/ui/src/api/index.ts @@ -1,3 +1,4 @@ +export * from './Admin' export * from './Billing' export * from './Dashboard' export * from './Model' diff --git a/ui/src/api/types.ts b/ui/src/api/types.ts index b31047e..7b73905 100644 --- a/ui/src/api/types.ts +++ b/ui/src/api/types.ts @@ -185,6 +185,40 @@ export interface DomainCreateModelReq { provider?: string; } +export interface DomainCustomOAuth { + /** 自定义OAuth访问令牌URL */ + access_token_url?: string; + /** 自定义OAuth授权URL */ + authorize_url?: string; + /** 用户信息回包中的头像URL字段名` */ + avatar_field?: string; + /** 自定义客户端ID */ + client_id?: string; + /** 自定义客户端密钥 */ + client_secret?: string; + /** 用户信息回包中的邮箱字段名 */ + email_field?: string; + /** 自定义OAuth开关 */ + enable?: boolean; + /** 用户信息回包中的ID字段名 */ + id_field?: string; + /** 用户信息回包中的用户名字段名` */ + name_field?: string; + /** 自定义OAuth Scope列表 */ + scopes?: string[]; + /** 自定义OAuth用户信息URL */ + userinfo_url?: string; +} + +export interface DomainDingtalkOAuth { + /** 钉钉客户端ID */ + client_id?: string; + /** 钉钉客户端密钥 */ + client_secret?: string; + /** 钉钉OAuth开关 */ + enable?: boolean; +} + export interface DomainIPInfo { /** ASN */ asn?: string; @@ -357,24 +391,12 @@ export interface DomainRegisterReq { export interface DomainSetting { /** 创建时间 */ created_at?: number; - /** 自定义OAuth访问令牌URL */ - custom_oauth_access_token_url?: string; - /** 自定义OAuth授权URL */ - custom_oauth_authorize_url?: string; - /** 自定义OAuth客户端ID */ - custom_oauth_client_id?: string; - /** 自定义OAuth Scope列表 */ - custom_oauth_scopes?: string[]; - /** 自定义OAuth用户信息URL */ - custom_oauth_userinfo_url?: string; - /** 钉钉客户端ID */ - dingtalk_client_id?: string; + /** 自定义OAuth接入 */ + custom_oauth?: DomainCustomOAuth; + /** 钉钉OAuth接入 */ + dingtalk_oauth?: DomainDingtalkOAuth; /** 是否禁用密码登录 */ disable_password_login?: boolean; - /** 是否开启自定义OAuth */ - enable_custom_oauth?: boolean; - /** 是否开启钉钉OAuth */ - enable_dingtalk_oauth?: boolean; /** 是否开启SSO */ enable_sso?: boolean; /** 是否强制两步验证 */ @@ -461,28 +483,12 @@ export interface DomainUpdateModelReq { } export interface DomainUpdateSettingReq { - /** 自定义OAuth访问令牌URL */ - custom_oauth_access_token_url?: string; - /** 自定义OAuth授权URL */ - custom_oauth_authorize_url?: string; - /** 自定义OAuth客户端ID */ - custom_oauth_client_id?: string; - /** 自定义OAuth客户端密钥 */ - custom_oauth_client_secret?: string; - /** 自定义OAuth Scope列表 */ - custom_oauth_scopes?: string[]; - /** 自定义OAuth用户信息URL */ - custom_oauth_userinfo_url?: string; - /** 钉钉客户端ID */ - dingtalk_client_id?: string; - /** 钉钉客户端密钥 */ - dingtalk_client_secret?: string; + /** 自定义OAuth配置 */ + custom_oauth?: DomainCustomOAuth; + /** 钉钉OAuth配置 */ + dingtalk_oauth?: DomainDingtalkOAuth; /** 是否禁用密码登录 */ disable_password_login?: boolean; - /** 是否开启自定义OAuth */ - enable_custom_oauth?: boolean; - /** 是否开启钉钉OAuth */ - enable_dingtalk_oauth?: boolean; /** 是否开启SSO */ enable_sso?: boolean; /** 是否强制两步验证 */ @@ -499,6 +505,8 @@ export interface DomainUpdateUserReq { } export interface DomainUser { + /** 头像URL */ + avatar_url?: string; /** 创建时间 */ created_at?: number; /** 邮箱 */ diff --git a/ui/src/pages/admin/adminTable.tsx b/ui/src/pages/admin/adminTable.tsx index 9df63c6..310a03f 100644 --- a/ui/src/pages/admin/adminTable.tsx +++ b/ui/src/pages/admin/adminTable.tsx @@ -7,9 +7,9 @@ import { TextField, Paper, } from '@mui/material'; -import { postCreateAdmin } from '@/api/User'; +import { postCreateAdmin } from '@/api/Admin'; import { CopyToClipboard } from 'react-copy-to-clipboard'; -import { deleteDeleteAdmin, getListAdminUser } from '@/api/User'; +import { deleteDeleteAdmin, getListAdminUser } from '@/api/Admin'; import { Table, Modal, message } from '@c-x/ui'; import { ColumnsType } from '@c-x/ui/dist/Table'; import { useRequest } from 'ahooks'; diff --git a/ui/src/pages/admin/loginHistory.tsx b/ui/src/pages/admin/loginHistory.tsx index 1018254..0e3d770 100644 --- a/ui/src/pages/admin/loginHistory.tsx +++ b/ui/src/pages/admin/loginHistory.tsx @@ -3,7 +3,7 @@ import { Stack, Box } from '@mui/material'; import { Table } from '@c-x/ui'; import dayjs from 'dayjs'; import { useRequest } from 'ahooks'; -import { getAdminLoginHistory } from '@/api/User'; +import { getAdminLoginHistory } from '@/api/Admin'; import { ColumnsType } from '@c-x/ui/dist/Table'; import { DomainListAdminLoginHistoryResp } from '@/api/types'; import User from '@/components/user'; diff --git a/ui/src/pages/auth/index.tsx b/ui/src/pages/auth/index.tsx index 392723d..0ce0912 100644 --- a/ui/src/pages/auth/index.tsx +++ b/ui/src/pages/auth/index.tsx @@ -21,11 +21,13 @@ import { Icon, message } from '@c-x/ui'; import { AestheticFluidBg } from '@/assets/jsm/AestheticFluidBg.module.js'; import { useSearchParams } from 'react-router-dom'; -import { postLogin, getUserOauthSignupOrIn, getGetSetting } from '@/api/User'; +import { postLogin, getUserOauthSignupOrIn } from '@/api/User'; +import { getGetSetting } from '@/api/Admin'; import { useForm, Controller } from 'react-hook-form'; import { styled } from '@mui/material/styles'; import { useRequest } from 'ahooks'; +import { DomainSetting } from '@/api/types'; // 样式化组件 const StyledContainer = styled(Container)(({ theme }) => ({ @@ -114,8 +116,9 @@ const AuthPage = () => { const [showPassword, setShowPassword] = useState(false); const [searchParams] = useSearchParams(); - const { data: loginSetting = {} } = useRequest(getGetSetting); - + const { data: loginSetting = {} as DomainSetting } = + useRequest(getGetSetting); + const { custom_oauth = {}, dingtalk_oauth = {} } = loginSetting; const { control, handleSubmit, @@ -175,10 +178,8 @@ const AuthPage = () => { }, []); const oauthEnable = useMemo(() => { - return ( - loginSetting.enable_custom_oauth || loginSetting.enable_dingtalk_oauth - ); - }, [loginSetting]); + return custom_oauth.enable || dingtalk_oauth.enable; + }, [custom_oauth, dingtalk_oauth]); // 渲染用户名输入框 const renderUsernameField = () => ( @@ -290,7 +291,7 @@ const AuthPage = () => { 使用其他方式登录 - {loginSetting.enable_dingtalk_oauth && ( + {dingtalk_oauth.enable && ( onOauthLogin('dingtalk')} @@ -298,7 +299,7 @@ const AuthPage = () => { )} - {loginSetting.enable_custom_oauth && ( + {custom_oauth.enable && ( onOauthLogin('custom')} diff --git a/ui/src/pages/invite/index.tsx b/ui/src/pages/invite/index.tsx index b26151c..4613312 100644 --- a/ui/src/pages/invite/index.tsx +++ b/ui/src/pages/invite/index.tsx @@ -23,12 +23,10 @@ import { Divider, } from '@mui/material'; import { useRequest } from 'ahooks'; -import { - postRegister, - getUserOauthSignupOrIn, - getGetSetting, -} from '@/api/User'; +import { postRegister, getUserOauthSignupOrIn } from '@/api/User'; +import { getGetSetting } from '@/api/Admin'; import { Icon } from '@c-x/ui'; +import { DomainSetting } from '@/api/types'; import DownloadIcon from '@mui/icons-material/Download'; import MenuBookIcon from '@mui/icons-material/MenuBook'; @@ -92,7 +90,9 @@ const StyledTextField = styled(TextField)(({ theme }) => ({ const Invite = () => { const { id, step } = useParams(); const [showPassword, setShowPassword] = useState(false); - const { data: loginSetting = {} } = useRequest(getGetSetting); + const { data: loginSetting = {} as DomainSetting } = + useRequest(getGetSetting); + const { custom_oauth = {}, dingtalk_oauth = {} } = loginSetting; const { control, handleSubmit, @@ -148,10 +148,8 @@ const Invite = () => { }; const oauthEnable = useMemo(() => { - return ( - loginSetting.enable_custom_oauth || loginSetting.enable_dingtalk_oauth - ); - }, [loginSetting]); + return custom_oauth.enable || dingtalk_oauth.enable; + }, [custom_oauth, dingtalk_oauth]); const oauthLogin = () => { return ( @@ -159,7 +157,7 @@ const Invite = () => { 使用以下方式注册 - {loginSetting.enable_dingtalk_oauth && ( + {dingtalk_oauth.enable && ( )} - {loginSetting.enable_custom_oauth && ( + {custom_oauth.enable && ( onOauthLogin('custom')} diff --git a/ui/src/pages/login/index.tsx b/ui/src/pages/login/index.tsx index 7e3b548..6a2263c 100644 --- a/ui/src/pages/login/index.tsx +++ b/ui/src/pages/login/index.tsx @@ -12,7 +12,7 @@ import { InputAdornment, IconButton, } from '@mui/material'; -import { postAdminLogin } from '@/api/User'; +import { postAdminLogin } from '@/api/Admin'; import { useForm, Controller } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import { styled } from '@mui/material/styles'; diff --git a/ui/src/pages/user-management/index.tsx b/ui/src/pages/user-management/index.tsx index 89d149d..b5db601 100644 --- a/ui/src/pages/user-management/index.tsx +++ b/ui/src/pages/user-management/index.tsx @@ -8,9 +8,8 @@ import { Button, Box, } from '@mui/material'; -import { Icon, Modal } from '@c-x/ui'; import { useRequest } from 'ahooks'; -import { getGetSetting, putUpdateSetting } from '@/api/User'; +import { getGetSetting, putUpdateSetting } from '@/api/Admin'; import MemberManage from './memberManage'; import LoginHistory from './loginHistory'; import { message } from '@c-x/ui'; @@ -30,11 +29,11 @@ const StyledLabel = styled('div')(({ theme }) => ({ color: theme.vars.palette.text.primary, })); -const OAUTH_LOGIN_TYPE_KEYS = ['enable_custom_oauth', 'enable_dingtalk_oauth']; +const OAUTH_LOGIN_TYPE_KEYS = ['dingtalk_oauth', 'custom_oauth']; const OAUTH_LOGIN_TYPE_LABELS = { - enable_custom_oauth: '已开启 OAuth 登录', - enable_dingtalk_oauth: '已开启钉钉登录', + custom_oauth: '已开启 OAuth 登录', + dingtalk_oauth: '已开启钉钉登录', }; type OAUTH_LOGIN_TYPE_KEYS = keyof typeof OAUTH_LOGIN_TYPE_LABELS; @@ -42,16 +41,7 @@ type OAUTH_LOGIN_TYPE_KEYS = keyof typeof OAUTH_LOGIN_TYPE_LABELS; const User = () => { const [thirdPartyLoginSettingModalOpen, setThirdPartyLoginSettingModalOpen] = useState(false); - const { - data = { - enable_sso: false, - force_two_factor_auth: false, - disable_password_login: false, - enable_dingtalk_oauth: false, - enable_custom_oauth: false, - }, - refresh, - } = useRequest(getGetSetting); + const { data, refresh } = useRequest(getGetSetting); const { runAsync: updateSetting } = useRequest(putUpdateSetting, { manual: true, @@ -62,8 +52,9 @@ const User = () => { }); const oauthLabel = useMemo(() => { + if (!data) return '未开启'; const key = OAUTH_LOGIN_TYPE_KEYS.find( - (key) => data[key as OAUTH_LOGIN_TYPE_KEYS] + (key) => data[key as OAUTH_LOGIN_TYPE_KEYS]?.enable ); return key ? OAUTH_LOGIN_TYPE_LABELS[key as OAUTH_LOGIN_TYPE_KEYS] @@ -127,7 +118,7 @@ const User = () => { setThirdPartyLoginSettingModalOpen(false)} - settingData={data} + settingData={data || {}} onOk={() => { refresh(); }} diff --git a/ui/src/pages/user-management/thirdPartyLoginSettingModal.tsx b/ui/src/pages/user-management/thirdPartyLoginSettingModal.tsx index 427990d..866bc77 100644 --- a/ui/src/pages/user-management/thirdPartyLoginSettingModal.tsx +++ b/ui/src/pages/user-management/thirdPartyLoginSettingModal.tsx @@ -1,9 +1,16 @@ -import { Button, Radio, Stack, Box, TextField } from '@mui/material'; -import { Modal, Icon, message } from '@c-x/ui'; +import { + Button, + Radio, + Stack, + TextField, + Autocomplete, + Chip, +} from '@mui/material'; +import { Modal, message } from '@c-x/ui'; import { useState, useEffect } from 'react'; import { useForm, Controller } from 'react-hook-form'; import { FormItem } from '@/components/form'; -import { putUpdateSetting } from '@/api/User'; +import { putUpdateSetting } from '@/api/Admin'; import { DomainSetting, DomainUpdateSettingReq } from '@/api/types'; type LoginType = 'dingding' | 'wechat' | 'feishu' | 'oauth' | 'none'; @@ -59,22 +66,33 @@ const ThirdPartyLoginSettingModal = ({ control, handleSubmit, reset, + watch, formState: { errors }, } = useForm({ defaultValues: { dingtalk_client_id: '', dingtalk_client_secret: '', - custom_oauth_access_token_url: '', - custom_oauth_authorize_url: '', - custom_oauth_client_id: '', - custom_oauth_client_secret: '', + access_token_url: '', + authorize_url: '', + client_id: '', + client_secret: '', + id_field: '', + name_field: '', + scopes: [] as string[], + avatar_field: '', + userinfo_url: '', + email_field: '', }, }); const [loginType, setLoginType] = useState( - settingData?.enable_dingtalk_oauth ? 'dingding' : 'none' + settingData?.dingtalk_oauth?.enable ? 'dingding' : 'none' ); + const [scopeInputValue, setScopeInputValue] = useState(''); + + const userInfoUrl = watch('userinfo_url'); + useEffect(() => { if (open) { reset(); @@ -82,25 +100,31 @@ const ThirdPartyLoginSettingModal = ({ }, [open]); useEffect(() => { - if (settingData?.enable_dingtalk_oauth) { + if (settingData?.dingtalk_oauth?.enable) { setLoginType('dingding'); reset( { - dingtalk_client_id: settingData.dingtalk_client_id, + dingtalk_client_id: settingData.dingtalk_oauth.client_id, + dingtalk_client_secret: settingData.dingtalk_oauth.client_secret, }, { keepValues: true, } ); } - if (settingData?.enable_custom_oauth) { + if (settingData?.custom_oauth?.enable) { setLoginType('oauth'); reset( { - custom_oauth_access_token_url: - settingData.custom_oauth_access_token_url, - custom_oauth_authorize_url: settingData.custom_oauth_authorize_url, - custom_oauth_client_id: settingData.custom_oauth_client_id, + access_token_url: settingData.custom_oauth.access_token_url, + authorize_url: settingData.custom_oauth.authorize_url, + client_id: settingData.custom_oauth.client_id, + id_field: settingData.custom_oauth.id_field, + name_field: settingData.custom_oauth.name_field, + scopes: settingData.custom_oauth.scopes || [], + avatar_field: settingData.custom_oauth.avatar_field, + userinfo_url: settingData.custom_oauth.userinfo_url, + email_field: settingData.custom_oauth.email_field, }, { keepValues: true, @@ -113,24 +137,42 @@ const ThirdPartyLoginSettingModal = ({ let params: DomainUpdateSettingReq = {}; if (loginType === 'none') { params = { - enable_dingtalk_oauth: false, - enable_custom_oauth: false, + dingtalk_oauth: { + enable: false, + }, + custom_oauth: { + enable: false, + }, }; } else if (loginType === 'dingding') { params = { - enable_dingtalk_oauth: true, - enable_custom_oauth: false, - dingtalk_client_id: data.dingtalk_client_id, - dingtalk_client_secret: data.dingtalk_client_secret, + dingtalk_oauth: { + enable: true, + client_id: data.dingtalk_client_id, + client_secret: data.dingtalk_client_secret, + }, + custom_oauth: { + enable: false, + }, }; } else if (loginType === 'oauth') { params = { - enable_custom_oauth: true, - enable_dingtalk_oauth: false, - custom_oauth_access_token_url: data.custom_oauth_access_token_url, - custom_oauth_authorize_url: data.custom_oauth_authorize_url, - custom_oauth_client_id: data.custom_oauth_client_id, - custom_oauth_client_secret: data.custom_oauth_client_secret, + dingtalk_oauth: { + enable: false, + }, + custom_oauth: { + enable: true, + access_token_url: data.access_token_url, + authorize_url: data.authorize_url, + client_id: data.client_id, + client_secret: data.client_secret, + id_field: data.id_field, + name_field: data.name_field, + scopes: data.scopes, + avatar_field: data.avatar_field, + userinfo_url: data.userinfo_url, + email_field: data.email_field, + }, }; } @@ -201,15 +243,15 @@ const ThirdPartyLoginSettingModal = ({ rules={{ required: 'Access Token URL 不能为空', }} - name='custom_oauth_access_token_url' + name='access_token_url' render={({ field }) => ( )} /> @@ -217,7 +259,7 @@ const ThirdPartyLoginSettingModal = ({ )} /> @@ -236,7 +278,7 @@ const ThirdPartyLoginSettingModal = ({ )} /> @@ -255,7 +297,7 @@ const ThirdPartyLoginSettingModal = ({ )} /> + + + { + if (value.length === 0) { + return 'Scope 不能为空'; + } + return true; + }, + }} + render={({ field }) => ( + { + field.onChange(value); + }} + onInputChange={(_, value) => { + setScopeInputValue(value); + }} + size='small' + freeSolo + renderTags={(value: readonly string[], getTagProps) => + value.map((option: string, index: number) => { + const { key, ...tagProps } = getTagProps({ index }); + const label = `${option}`; + return ( + + ); + }) + } + renderInput={(params) => ( + { + // 失去焦点时自动添加当前输入的值 + const trimmedValue = scopeInputValue.trim(); + if (trimmedValue && !field.value.includes(trimmedValue)) { + field.onChange([...field.value, trimmedValue]); + // 清空输入框 + setScopeInputValue(''); + } + }} + /> + )} + /> + )} + /> + + + ( + + )} + /> + + {userInfoUrl && ( + <> + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + )} ); }; diff --git a/ui/src/pages/user/dashboard/index.tsx b/ui/src/pages/user/dashboard/index.tsx index 015ff98..9185d88 100644 --- a/ui/src/pages/user/dashboard/index.tsx +++ b/ui/src/pages/user/dashboard/index.tsx @@ -64,12 +64,7 @@ const Dashboard = () => { - + ); }; diff --git a/ui/src/pages/user/login/index.tsx b/ui/src/pages/user/login/index.tsx index 6cbd09f..0e2184a 100644 --- a/ui/src/pages/user/login/index.tsx +++ b/ui/src/pages/user/login/index.tsx @@ -22,11 +22,13 @@ import { getRedirectUrl } from '@/utils'; import { AestheticFluidBg } from '@/assets/jsm/AestheticFluidBg.module.js'; import { useSearchParams } from 'react-router-dom'; -import { postLogin, getUserOauthSignupOrIn, getGetSetting } from '@/api/User'; +import { postLogin, getUserOauthSignupOrIn } from '@/api/User'; +import { getGetSetting } from '@/api/Admin'; import { useForm, Controller } from 'react-hook-form'; import { styled } from '@mui/material/styles'; import { useRequest } from 'ahooks'; +import { DomainSetting } from '@/api/types'; // 样式化组件 const StyledContainer = styled(Container)(({ theme }) => ({ @@ -115,8 +117,9 @@ const UserLogin = () => { const [showPassword, setShowPassword] = useState(false); const [searchParams] = useSearchParams(); - const { data: loginSetting = {} } = useRequest(getGetSetting); - + const { data: loginSetting = {} as DomainSetting } = + useRequest(getGetSetting); + const { custom_oauth = {}, dingtalk_oauth = {} } = loginSetting; const { control, handleSubmit, @@ -166,10 +169,8 @@ const UserLogin = () => { }, []); const oauthEnable = useMemo(() => { - return ( - loginSetting.enable_custom_oauth || loginSetting.enable_dingtalk_oauth - ); - }, [loginSetting]); + return custom_oauth.enable || dingtalk_oauth.enable; + }, [custom_oauth, dingtalk_oauth]); // 渲染用户名输入框 const renderUsernameField = () => ( @@ -280,7 +281,7 @@ const UserLogin = () => { 使用其他方式登录 - {loginSetting.enable_dingtalk_oauth && ( + {dingtalk_oauth.enable && ( onOauthLogin('dingtalk')} @@ -288,7 +289,7 @@ const UserLogin = () => { )} - {loginSetting.enable_custom_oauth && ( + {custom_oauth.enable && ( onOauthLogin('custom')}