diff --git a/backend/alembic/versions/060_platform_token_ddl.py b/backend/alembic/versions/060_platform_token_ddl.py new file mode 100644 index 00000000..3def2d33 --- /dev/null +++ b/backend/alembic/versions/060_platform_token_ddl.py @@ -0,0 +1,37 @@ +"""060_platform_token_ddl + +Revision ID: b40e41c67db3 +Revises: db1a95567cbb +Create Date: 2026-01-04 15:50:31.550287 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'b40e41c67db3' +down_revision = 'db1a95567cbb' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('sys_platform_token', + sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column('token', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False), + sa.Column('create_time', sa.BigInteger(), nullable=False), + sa.Column('exp_time', sa.BigInteger(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_sys_platform_token_id'), 'sys_platform_token', ['id'], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_sys_platform_token_id'), table_name='sys_platform_token') + op.drop_table('sys_platform_token') + # ### end Alembic commands ### diff --git a/backend/common/utils/whitelist.py b/backend/common/utils/whitelist.py index d9df569b..8a11d1d2 100644 --- a/backend/common/utils/whitelist.py +++ b/backend/common/utils/whitelist.py @@ -36,6 +36,8 @@ "/system/authentication/platform/status", "/system/authentication/login/*", "/system/authentication/sso/*", + "/system/platform/sso/*", + "/system/platform/client/*", "/system/parameter/login" ] diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 9faeba21..256396ec 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "pyyaml (>=6.0.2,<7.0.0)", "fastapi-mcp (>=0.3.4,<0.4.0)", "tabulate>=0.9.0", - "sqlbot-xpack>=0.0.4.0,<0.0.5.0", + "sqlbot-xpack>=0.0.5.0,<0.0.6.0", "fastapi-cache2>=0.2.2", "sqlparse>=0.5.3", "redis>=6.2.0", diff --git a/frontend/src/assets/svg/logo_wechat-work.svg b/frontend/src/assets/svg/logo_wechat-work.svg new file mode 100644 index 00000000..99ac1d3f --- /dev/null +++ b/frontend/src/assets/svg/logo_wechat-work.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/views/login/xpack/DingtalkQr.vue b/frontend/src/views/login/xpack/DingtalkQr.vue new file mode 100644 index 00000000..9b1d3b98 --- /dev/null +++ b/frontend/src/views/login/xpack/DingtalkQr.vue @@ -0,0 +1,79 @@ + + + + + + diff --git a/frontend/src/views/login/xpack/Handler.vue b/frontend/src/views/login/xpack/Handler.vue index d5fb2a1a..8f7ab3d6 100644 --- a/frontend/src/views/login/xpack/Handler.vue +++ b/frontend/src/views/login/xpack/Handler.vue @@ -1,5 +1,5 @@ - + {{ t('login.other_login') @@ -54,13 +54,11 @@ import { ref, onMounted, nextTick, computed } from 'vue' import QrcodeLdap from './QrcodeLdap.vue' import LdapLoginForm from './LdapLoginForm.vue' -/* import Oidc from './Oidc.vue' -import Oauth2 from './Oauth2.vue' -import Saml2 from './Saml2.vue' */ + import Oidc from './Oidc.vue' import Cas from './Cas.vue' import Oauth2 from './Oauth2.vue' -// import QrTab from './QrTab.vue' +import QrTab from './QrTab.vue' import { request } from '@/utils/request' import { useCache } from '@/utils/useCache' @@ -197,6 +195,8 @@ const init = (cb?: () => void) => { .then((res) => { if (res) { const list: any[] = res as any[] + /* list.push({ name: 'qrcode', enable: true }) + list.push({ name: 'wecom', enable: true }) */ list.forEach((item: { name: keyof LoginCategory; enable: boolean }) => { loginCategory.value[item.name] = item.enable if (item.enable) { @@ -416,6 +416,72 @@ const oidcLogin = () => { }, 1500) }) } +const wecomLogin = () => { + const urlParams = getUrlParams() + request + .post('/system/platform/sso/6', urlParams) + .then((res: any) => { + const token = res.access_token + // const platform_info = res.platform_info + if (token && isPlatformClient()) { + wsCache.set('de-platform-client', true) + } + userStore.setToken(token) + userStore.setExp(res.exp) + userStore.setTime(Date.now()) + userStore.setPlatformInfo({ + flag: 'wecom', + // data: platform_info ? JSON.stringify(platform_info) : '', + origin: 6, + }) + const queryRedirectPath = getCurLocation() + router.push({ path: queryRedirectPath }) + }) + .catch((e: any) => { + userStore.setToken('') + setTimeout(() => { + // logoutHandler(true, true) + platformLoginMsg.value = e?.message || e + setTimeout(() => { + window.location.href = + window.location.origin + window.location.pathname + window.location.hash + }, 2000) + }, 1500) + }) +} +const dingtalkLogin = () => { + const urlParams = getUrlParams() + request + .post('/system/platform/sso/7', urlParams) + .then((res: any) => { + const token = res.access_token + // const platform_info = res.platform_info + if (token && isPlatformClient()) { + wsCache.set('de-platform-client', true) + } + userStore.setToken(token) + userStore.setExp(res.exp) + userStore.setTime(Date.now()) + userStore.setPlatformInfo({ + flag: 'dingtalk', + // data: platform_info ? JSON.stringify(platform_info) : '', + origin: 7, + }) + const queryRedirectPath = getCurLocation() + router.push({ path: queryRedirectPath }) + }) + .catch((e: any) => { + userStore.setToken('') + setTimeout(() => { + // logoutHandler(true, true) + platformLoginMsg.value = e?.message || e + setTimeout(() => { + window.location.href = + window.location.origin + window.location.pathname + window.location.hash + }, 2000) + }, 1500) + }) +} /* const platformLogin = (origin: number) => { const url = '/system/authentication/sso/cas' request @@ -591,6 +657,10 @@ onMounted(() => { oauth2Login() } else if (state?.includes('oidc')) { oidcLogin() + } else if (state?.includes('wecom')) { + wecomLogin() + } else if (state?.includes('dingtalk')) { + dingtalkLogin() } else { auto2Platform() } diff --git a/frontend/src/views/login/xpack/PlatformClient.ts b/frontend/src/views/login/xpack/PlatformClient.ts index 9d7c804b..856be249 100644 --- a/frontend/src/views/login/xpack/PlatformClient.ts +++ b/frontend/src/views/login/xpack/PlatformClient.ts @@ -1,9 +1,10 @@ import { loadScript } from '@/utils/RemoteJs' import { getQueryString } from '@/utils/utils' import { ElMessage, ElMessageBox } from 'element-plus-secondary' -import { request } from '@/utils/request' + // import { useI18n } from 'vue-i18n' import { i18n } from '@/i18n' +import { queryClientInfo } from './platformUtils' declare global { interface Window { tt: any @@ -26,7 +27,7 @@ export interface LoginCategory { const t = i18n.global.t const flagArray = ['dingtalk', 'lark', 'larksuite'] const urlArray = [ - 'https://g.alicdn.com/dingding/dingtalk-jsapi/3.0.25/dingtalk.open.js', + 'https://g.alicdn.com/dingding/dingtalk-jsapi/3.1.0/dingtalk.open.js', 'https://lf1-cdn-tos.bytegoofy.com/goofy/lark/op/h5-js-sdk-1.5.26.js', 'https://lf1-cdn-tos.bytegoofy.com/goofy/lark/op/h5-js-sdk-1.5.16.js', ] @@ -49,12 +50,7 @@ export const loadClient = (category: LoginCategory) => { .catch(() => {}) return false } - if ( - !type || - !flagArray.includes(type) || - !category[type as keyof LoginCategory] || - (type === 'dingtalk' && !corpid) - ) { + if (!type || !flagArray.includes(type) || !category[type as keyof LoginCategory]) { return false } const index = flagArray.indexOf(type) @@ -62,7 +58,7 @@ export const loadClient = (category: LoginCategory) => { const awaitMethod = loadScript(urlArray[index], jsId) awaitMethod .then(() => { - if (index === 0 && corpid) { + if (index === 0) { dingtalkClientRequest(corpid) } if (index === 1) { @@ -78,12 +74,17 @@ export const loadClient = (category: LoginCategory) => { return true } -const dingtalkClientRequest = (id?: string) => { +const dingtalkClientRequest = async (id: string | null) => { const dd = window['dd'] - if (id && dd?.runtime?.permission?.requestAuthCode) { - dd.runtime.permission.requestAuthCode({ - corpId: id, + if (dd?.requestAuthCode) { + const clientInfoRes = await queryClientInfo(7) + const corpId = id || clientInfoRes['corpid'] + const client_id = clientInfoRes['client_id'] + dd.requestAuthCode({ + corpId: corpId, + clientId: client_id, onSuccess: function (result: any) { + ElMessage.success(JSON.stringify(result)) const code = result.code const state = `fit2cloud-dingtalk-client` toUrl(`?code=${code}&state=${state}`) @@ -102,8 +103,8 @@ const larkClientRequest = async () => { ElMessage.error('load remote lark js error') return } - const res = await queryAppid('lark') - if (!res?.data?.appId) { + const res = await queryClientInfo(8) + if (!res?.appId) { ElMessage.error('get appId error') return } @@ -150,7 +151,7 @@ const larksuiteClientRequest = async () => { ElMessage.error('load remote lark js error') return } - const res = await queryAppid('larksuite') + const res = await queryClientInfo(9) if (!res?.data?.appId) { ElMessage.error('get appId error') return @@ -173,11 +174,6 @@ const larksuiteClientRequest = async () => { }) } -const queryAppid = (type: string) => { - const url = `/${type}/qrinfo` - return request.get(url) -} - const toUrl = (url: string) => { const { origin, pathname } = window.location window.location.href = origin + pathname + url diff --git a/frontend/src/views/login/xpack/QrTab.vue b/frontend/src/views/login/xpack/QrTab.vue new file mode 100644 index 00000000..261d5343 --- /dev/null +++ b/frontend/src/views/login/xpack/QrTab.vue @@ -0,0 +1,129 @@ + + + + + + + + + + + + + {{ t('user.wechat_for_business') }} + + + + + + + + + + + {{ t('user.dingtalk') }} + + + + + + + + + + + {{ t('threshold.lark') }} + + + + + + + + + + + {{ t('system.international_feishu') }} + + + + + + + + + + diff --git a/frontend/src/views/login/xpack/WecomQr.vue b/frontend/src/views/login/xpack/WecomQr.vue new file mode 100644 index 00000000..3ad14179 --- /dev/null +++ b/frontend/src/views/login/xpack/WecomQr.vue @@ -0,0 +1,93 @@ + + + + + + diff --git a/frontend/src/views/login/xpack/platformUtils.ts b/frontend/src/views/login/xpack/platformUtils.ts new file mode 100644 index 00000000..3607e0e6 --- /dev/null +++ b/frontend/src/views/login/xpack/platformUtils.ts @@ -0,0 +1,5 @@ +import { request } from '@/utils/request' +export const queryClientInfo = (origin: number) => { + const url = `/system/platform/client/${origin}` + return request.get(url) +}