feat: dingtalk (#1340)
Co-authored-by: sealday <sealday@gmail.com> Reviewed-on: daoyoucloud/tachybase#1340
This commit is contained in:
parent
456df870a3
commit
aedcf15863
@ -58,7 +58,7 @@
|
||||
"react-js-cron": "^3.1.0",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router": "^6.11.2",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-router-dom": "^6.25.1",
|
||||
"react-to-print": "^2.14.7",
|
||||
"sanitize-html": "2.10.0",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
|
@ -12,7 +12,7 @@
|
||||
"antd-mobile-icons": "^0.3.0",
|
||||
"lodash": "4.17.21",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "6.x"
|
||||
"react-router-dom": "~6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
|
@ -31,7 +31,7 @@
|
||||
"react-error-boundary": "^4.0.10",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-pdf": "^7.5.1",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-router-dom": "^6.25.1",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-zoom-pan-pinch": "^3.4.4",
|
||||
"redis": "^4.6.11",
|
||||
|
@ -22,7 +22,7 @@
|
||||
"qrcode": "^1.5.1",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "~18.3.1",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-router-dom": "^6.25.1",
|
||||
"sequelize": "^6.26.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -7,7 +7,7 @@
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.3.7",
|
||||
"antd-mobile": "^5.36.1",
|
||||
"react-router-dom": "^6.23.1"
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
|
@ -15,7 +15,7 @@
|
||||
"antd": "5.19.1",
|
||||
"lodash": "4.17.21",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "^6.11.2"
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
|
@ -12,7 +12,7 @@
|
||||
"@tachybase/schema": "workspace:*",
|
||||
"antd": "5.19.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "^6.11.2"
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
|
@ -14,7 +14,7 @@
|
||||
"cron": "^3.1.7",
|
||||
"react": "^18.3.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "^6.11.2"
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/actions": "workspace:*",
|
||||
|
@ -12,7 +12,7 @@
|
||||
"@ant-design/icons": "~5.3.7",
|
||||
"antd": "5.19.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "6.x"
|
||||
"react-router-dom": "~6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/actions": "workspace:*",
|
||||
|
@ -14,7 +14,7 @@
|
||||
"antd": "5.19.1",
|
||||
"lodash": "~4.17.21",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "6.x"
|
||||
"react-router-dom": "~6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/actions": "workspace:*",
|
||||
|
@ -16,7 +16,7 @@
|
||||
"antd": "5.19.1",
|
||||
"lodash": "4.17.21",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-router-dom": "^6.25.1",
|
||||
"sequelize": "^6.26.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
2
packages/plugins/@tachybase/plugin-dingtalk/.npmignore
Normal file
2
packages/plugins/@tachybase/plugin-dingtalk/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
/src
|
1
packages/plugins/@tachybase/plugin-dingtalk/README.md
Normal file
1
packages/plugins/@tachybase/plugin-dingtalk/README.md
Normal file
@ -0,0 +1 @@
|
||||
# @tachybase/plugin-dingding
|
2
packages/plugins/@tachybase/plugin-dingtalk/client.d.ts
vendored
Normal file
2
packages/plugins/@tachybase/plugin-dingtalk/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/client';
|
||||
export { default } from './dist/client';
|
1
packages/plugins/@tachybase/plugin-dingtalk/client.js
Normal file
1
packages/plugins/@tachybase/plugin-dingtalk/client.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client/index.js');
|
21
packages/plugins/@tachybase/plugin-dingtalk/package.json
Normal file
21
packages/plugins/@tachybase/plugin-dingtalk/package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@tachybase/plugin-dingtalk",
|
||||
"version": "0.21.74",
|
||||
"main": "dist/server/index.js",
|
||||
"devDependencies": {
|
||||
"@alicloud/dingtalk": "^2.1.30",
|
||||
"@alicloud/openapi-client": "^0.4.10",
|
||||
"@alicloud/tea-util": "^1.4.9",
|
||||
"@ant-design/icons": "^5.3.7",
|
||||
"antd": "5.19.1",
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/actions": "workspace:*",
|
||||
"@tachybase/auth": "workspace:*",
|
||||
"@tachybase/client": "workspace:*",
|
||||
"@tachybase/plugin-auth": "workspace:*",
|
||||
"@tachybase/server": "workspace:*",
|
||||
"@tachybase/test": "workspace:*"
|
||||
}
|
||||
}
|
2
packages/plugins/@tachybase/plugin-dingtalk/server.d.ts
vendored
Normal file
2
packages/plugins/@tachybase/plugin-dingtalk/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/server';
|
||||
export { default } from './dist/server';
|
1
packages/plugins/@tachybase/plugin-dingtalk/server.js
Normal file
1
packages/plugins/@tachybase/plugin-dingtalk/server.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/server/index.js');
|
@ -0,0 +1,104 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { SchemaComponent, useAPIClient, useApp } from '@tachybase/client';
|
||||
|
||||
import { CopyOutlined, LoginOutlined } from '@ant-design/icons';
|
||||
import { Button, Input, message, Typography } from 'antd';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { useTranslation } from './locale';
|
||||
|
||||
export const AdminSettingsForm = () => {
|
||||
const { t } = useTranslation();
|
||||
const app = useApp();
|
||||
const redirectUrl = React.useMemo(() => app.getApiUrl('dingtalk:redirect'), [app]);
|
||||
const onCopy = (text) => {
|
||||
navigator.clipboard.writeText(text), message.success(t('Copied'));
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<SchemaComponent
|
||||
scope={{ t }}
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
public: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
autoSignup: {
|
||||
'x-decorator': 'FormItem',
|
||||
type: 'boolean',
|
||||
title: '{{t("Sign up automatically when the user does not exist")}}',
|
||||
'x-component': 'Checkbox',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
dingtalk: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
clientId: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '{{t("Client ID")}}',
|
||||
},
|
||||
clientSecret: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '{{t("Client Secret")}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Title level={5} style={{ fontSize: '14px' }}>
|
||||
{t('Redirect URL')}
|
||||
<span style={{ marginLeft: '2px' }}>:</span>
|
||||
</Typography.Title>
|
||||
<Input value={redirectUrl} disabled={true} addonBefore={<CopyOutlined onClick={() => onCopy(redirectUrl)} />} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SignInButton = ({ authenticator }) => {
|
||||
const [href, setHref] = React.useState();
|
||||
const { t } = useTranslation();
|
||||
const api = useAPIClient();
|
||||
const location = useLocation();
|
||||
const urlSearchParams = new URLSearchParams(location.search);
|
||||
const redirect = urlSearchParams.get('redirect');
|
||||
useEffect(() => {
|
||||
href && (window.location.href = href);
|
||||
}, [href]);
|
||||
useEffect(() => {
|
||||
const authenticatorName = urlSearchParams.get('authenticator');
|
||||
const error = urlSearchParams.get('error');
|
||||
if (authenticatorName === authenticator.name && error) {
|
||||
message.error(t(error));
|
||||
return;
|
||||
}
|
||||
});
|
||||
const onClick = async () => {
|
||||
const result = await api.request({
|
||||
method: 'post',
|
||||
url: 'dingtalk:getAuthUrl',
|
||||
headers: { 'X-Authenticator': authenticator.name },
|
||||
data: { redirect: redirect },
|
||||
});
|
||||
const url = result?.data?.data?.url;
|
||||
if (url) {
|
||||
setHref(url);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Button block icon={<LoginOutlined />} shape="round" onClick={onClick}>
|
||||
{authenticator.title ? t(authenticator.title) : t('Sign in via DingTalk')}
|
||||
</Button>
|
||||
);
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
import { Plugin } from '@tachybase/client';
|
||||
import PluginAuthClient from '@tachybase/plugin-auth/client';
|
||||
|
||||
import { authType } from '../constants';
|
||||
import { AdminSettingsForm, SignInButton } from './components';
|
||||
|
||||
export class PluginDingding extends Plugin {
|
||||
async load() {
|
||||
this.app.pm.get(PluginAuthClient).registerType(authType, { components: { SignInButton, AdminSettingsForm } });
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginDingding;
|
@ -0,0 +1,7 @@
|
||||
import { useTranslation as useT } from '@tachybase/client';
|
||||
|
||||
import { namespace } from '../constants';
|
||||
|
||||
export function useTranslation() {
|
||||
return useT([namespace, 'client'], { nsMode: 'fallback' });
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export const authType = 'dingtalk';
|
||||
export const namespace = 'dingtalk';
|
2
packages/plugins/@tachybase/plugin-dingtalk/src/index.ts
Normal file
2
packages/plugins/@tachybase/plugin-dingtalk/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './server';
|
||||
export { default } from './server';
|
@ -0,0 +1,15 @@
|
||||
export default {
|
||||
"Sign in via DingTalk": "Sign in via DingTalk",
|
||||
"Client ID": "Client ID",
|
||||
"Client Secret": "Client Secret",
|
||||
"DingTalk": "DingTalk",
|
||||
"dingtalk": "dingtalk",
|
||||
"Redirect URL": "Redirect URL",
|
||||
"Please enter the authCode": "Please enter the authCode",
|
||||
"Authorizing Login": "Authorizing Login",
|
||||
"Sign up automatically when the user does not exist": "Sign up automatically when the user does not exist",
|
||||
"User not found": "User not found",
|
||||
"Failed to get mobile": "Failed to get mobile",
|
||||
"Failed to get accessToken": "Failed to get accessToken",
|
||||
"Copied": "Copied"
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
export default {
|
||||
"Sign in via DingTalk": "钉钉登录",
|
||||
"Client ID": "Client ID",
|
||||
"Client Secret": "Client Secret",
|
||||
"DingTalk": "钉钉",
|
||||
"dingtalk": "钉钉",
|
||||
"Redirect URL": "回调地址",
|
||||
"Please enter the authCode": "请输入authCode",
|
||||
"Authorizing Login": "授权登录中...",
|
||||
"Sign up automatically when the user does not exist": "用户不存在时自动注册",
|
||||
"User not found": "用户不存在",
|
||||
"Failed to get mobile": "未获取到手机号",
|
||||
"Failed to get accessToken": "未获取到accessToken",
|
||||
"Copied": "已复制"
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import { Context, Next } from '@tachybase/actions';
|
||||
import { AppSupervisor } from '@tachybase/server';
|
||||
|
||||
export const getAuthUrl = async (ctx: Context, next: Next) => {
|
||||
const { redirect } = ctx.action.params.values;
|
||||
const url = await ctx.auth.getAuthUrl(redirect);
|
||||
ctx.body = { url };
|
||||
await next();
|
||||
};
|
||||
|
||||
export const redirect = async (ctx: Context, next: Next) => {
|
||||
const { state } = ctx.action.params;
|
||||
const search = new URLSearchParams(decodeURIComponent(state));
|
||||
const authenticator = search.get('name');
|
||||
const appName = search.get('app');
|
||||
const redirect = search.get('redirect') || '/admin';
|
||||
let prefix = process.env.APP_PUBLIC_PATH || '';
|
||||
if (appName && appName !== 'main') {
|
||||
const appSupervisor = AppSupervisor.getInstance();
|
||||
if (appSupervisor?.runningMode !== 'single') {
|
||||
prefix += `apps/${appName}`;
|
||||
}
|
||||
}
|
||||
const auth = await ctx.app.authManager.get(authenticator, ctx);
|
||||
if (prefix.endsWith('/')) {
|
||||
prefix = prefix.slice(0, -1);
|
||||
}
|
||||
try {
|
||||
const { token } = await auth.signIn();
|
||||
ctx.redirect(`${prefix}${redirect}?authenticator=${authenticator}&token=${token}`);
|
||||
} catch (error) {
|
||||
ctx.logger.error('Dingtalk auth error', { error });
|
||||
ctx.redirect(`${prefix}/signin?redirect=${redirect}&authenticator=${authenticator}&error=${error.message}`);
|
||||
}
|
||||
await next();
|
||||
};
|
@ -0,0 +1,68 @@
|
||||
import { BaseAuth } from '@tachybase/auth';
|
||||
|
||||
import { namespace } from '../constants';
|
||||
import { DingtalkClient } from './client';
|
||||
|
||||
export class Auth extends BaseAuth {
|
||||
constructor(config) {
|
||||
const { ctx } = config;
|
||||
super({
|
||||
...config,
|
||||
userCollection: ctx.db.getCollection('users'),
|
||||
});
|
||||
}
|
||||
async validate() {
|
||||
const ctx = this.ctx;
|
||||
const { authCode } = ctx.action.params || {};
|
||||
if (!authCode) {
|
||||
ctx.throw(401, ctx.t('Please enter the authCode', { ns: namespace }));
|
||||
}
|
||||
const dingtalkClient = new DingtalkClient({
|
||||
clientId: this.options?.dingtalk?.clientId,
|
||||
clientSecret: this.options?.clientSecret,
|
||||
ctx: this.ctx,
|
||||
});
|
||||
const accessToken = await dingtalkClient.getAccessToken(authCode);
|
||||
if (!accessToken) {
|
||||
ctx.throw(401, ctx.t('Failed to get accessToken', { ns: namespace }));
|
||||
}
|
||||
const userInfo = await dingtalkClient.getUserInfo(accessToken);
|
||||
const { mobile, nick, email } = userInfo;
|
||||
if (!mobile) {
|
||||
ctx.throw(401, ctx.t('Failed to get mobile', { ns: namespace }));
|
||||
}
|
||||
const authenticator = this.authenticator;
|
||||
let user = await authenticator.findUser(mobile);
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
user = await this.userRepository.findOne({
|
||||
filter: { phone: mobile },
|
||||
});
|
||||
if (user) {
|
||||
await authenticator.addUser(user.id, {
|
||||
through: {
|
||||
uuid: mobile,
|
||||
},
|
||||
});
|
||||
return user;
|
||||
}
|
||||
const { autoSignup } = this.options?.public || {};
|
||||
if (!autoSignup) {
|
||||
ctx.throw(401, ctx.t('User not found', { ns: namespace }));
|
||||
}
|
||||
return await authenticator.newUser(mobile, {
|
||||
nickname: nick ?? null,
|
||||
phone: mobile ?? null,
|
||||
email: email ?? null,
|
||||
});
|
||||
}
|
||||
async getAuthUrl(redirect) {
|
||||
const clientId = this.options?.dingtalk?.clientId;
|
||||
const app = this.ctx.app.name;
|
||||
const redirectUrl = encodeURIComponent(`${this.ctx.origin}${process.env.API_BASE_PATH}dingtalk:redirect`);
|
||||
const state = encodeURIComponent(`redirect=${redirect}&app=${app}&name=${this.ctx.headers['x-authenticator']}`);
|
||||
const url = `https://login.dingtalk.com/oauth2/auth?client_id=${clientId}&response_type=code&scope=openid&state=${state}&redirect_uri=${redirectUrl}&prompt=consent`;
|
||||
return url;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import ContactClient, { GetUserHeaders } from '@alicloud/dingtalk/contact_1_0';
|
||||
import OAuthClient, { GetUserTokenRequest } from '@alicloud/dingtalk/oauth2_1_0';
|
||||
import { Config } from '@alicloud/openapi-client';
|
||||
import Util, { RuntimeOptions } from '@alicloud/tea-util';
|
||||
|
||||
export class DingtalkClient {
|
||||
config;
|
||||
ctx;
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
this.ctx = config?.ctx;
|
||||
}
|
||||
createAuthClient() {
|
||||
const config = new Config({});
|
||||
config.protocol = 'https';
|
||||
config.regionId = 'central';
|
||||
return new OAuthClient(config);
|
||||
}
|
||||
createContactClient() {
|
||||
const config = new Config({});
|
||||
config.protocol = 'https';
|
||||
config.regionId = 'central';
|
||||
return new ContactClient(config);
|
||||
}
|
||||
async getAccessToken(code) {
|
||||
const client = this.createAuthClient();
|
||||
const getUserTokenRequest = new GetUserTokenRequest({
|
||||
clientId: this.config.clientId,
|
||||
clientSecret: this.config.clientSecret,
|
||||
code,
|
||||
grantType: 'authorization_code',
|
||||
});
|
||||
try {
|
||||
const response = await client.getUserToken(getUserTokenRequest);
|
||||
return response?.body?.accessToken || '';
|
||||
} catch (err) {
|
||||
if (!Util.empty(err.code) && !Util.empty(err.message)) {
|
||||
this.ctx?.log.debug(`DingTalk get accessToken error code:${err.code}, message:${err.message}`);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
async getUserInfo(accessToken) {
|
||||
const client = this.createContactClient();
|
||||
const getUserHeaders = new GetUserHeaders({});
|
||||
getUserHeaders.xAcsDingtalkAccessToken = accessToken;
|
||||
try {
|
||||
const response = await client.getUserWithOptions('me', getUserHeaders, new RuntimeOptions({}));
|
||||
return response?.body || {};
|
||||
} catch (err) {
|
||||
if (!Util.empty(err.code) && !Util.empty(err.message)) {
|
||||
this.ctx?.log.debug(`DingTalk get userInfo error code:${err.code}, message:${err.message}`);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { default } from './plugin';
|
@ -0,0 +1,53 @@
|
||||
import { tval } from '@tachybase/client';
|
||||
import { Gateway, Plugin } from '@tachybase/server';
|
||||
|
||||
import { authType, namespace } from '../constants';
|
||||
import { getAuthUrl, redirect } from './actions/dingding';
|
||||
import { Auth } from './auth';
|
||||
|
||||
export class PluginDingdingServer extends Plugin {
|
||||
async afterAdd() {}
|
||||
|
||||
async beforeLoad() {}
|
||||
|
||||
async load() {
|
||||
console.log('hello world dingding');
|
||||
this.app.authManager.registerTypes(authType, {
|
||||
auth: Auth,
|
||||
title: tval('DingTalk', { ns: namespace }),
|
||||
});
|
||||
this.app.resourcer.define({
|
||||
name: 'dingtalk',
|
||||
actions: {
|
||||
getAuthUrl: getAuthUrl,
|
||||
redirect: redirect,
|
||||
},
|
||||
});
|
||||
this.app.acl.allow('dingtalk', '*', 'public');
|
||||
Gateway.getInstance().addAppSelectorMiddleware(async (ctx, next) => {
|
||||
const { req } = ctx;
|
||||
const url = new URL(req.url, `http://${req.headers.host}`);
|
||||
const params = url.searchParams;
|
||||
const state = params.get('state');
|
||||
if (!state) {
|
||||
return next();
|
||||
}
|
||||
const search = new URLSearchParams(state);
|
||||
const appName = search.get('app');
|
||||
if (appName) {
|
||||
ctx.resolvedAppName = appName;
|
||||
}
|
||||
await next();
|
||||
});
|
||||
}
|
||||
|
||||
async install() {}
|
||||
|
||||
async afterEnable() {}
|
||||
|
||||
async afterDisable() {}
|
||||
|
||||
async remove() {}
|
||||
}
|
||||
|
||||
export default PluginDingdingServer;
|
@ -25,7 +25,7 @@
|
||||
"lodash": "4.17.21",
|
||||
"react": "^18.3.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "^6.11.2"
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
|
@ -24,7 +24,7 @@
|
||||
"react": "~18.3.1",
|
||||
"react-dom": "~18.3.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "^6.11.2"
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/actions": "workspace:*",
|
||||
|
@ -19,7 +19,7 @@
|
||||
"react": "~18.3.1",
|
||||
"react-dom": "~18.3.1",
|
||||
"react-i18next": "~14.1.2",
|
||||
"react-router-dom": "6.x"
|
||||
"react-router-dom": "~6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
|
@ -17,7 +17,7 @@
|
||||
"pg": "^8.7.3",
|
||||
"react": "~18.3.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "^6.11.2"
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
|
@ -18,7 +18,7 @@
|
||||
"openid-client": "^5.4.2",
|
||||
"react": "~18.3.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "^6.11.2"
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/actions": "workspace:*",
|
||||
|
@ -25,7 +25,7 @@ export class OidcPlugin extends Plugin {
|
||||
});
|
||||
|
||||
// 注册接口
|
||||
this.app.resource({
|
||||
this.app.resourcer.define({
|
||||
name: 'oidc',
|
||||
actions: {
|
||||
getAuthUrl,
|
||||
|
@ -15,7 +15,7 @@
|
||||
"antd": "5.19.1",
|
||||
"react": "~18.3.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "^6.11.2"
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/actions": "workspace:*",
|
||||
|
@ -29,7 +29,7 @@
|
||||
"react": "~18.3.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-js-cron": "^3.1.0",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-router-dom": "^6.25.1",
|
||||
"sequelize": "^6.26.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
603
pnpm-lock.yaml
603
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user