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-js-cron": "^3.1.0",
|
||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
"react-router": "^6.11.2",
|
"react-router": "^6.11.2",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.25.1",
|
||||||
"react-to-print": "^2.14.7",
|
"react-to-print": "^2.14.7",
|
||||||
"sanitize-html": "2.10.0",
|
"sanitize-html": "2.10.0",
|
||||||
"use-deep-compare-effect": "^1.8.1"
|
"use-deep-compare-effect": "^1.8.1"
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"antd-mobile-icons": "^0.3.0",
|
"antd-mobile-icons": "^0.3.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "6.x"
|
"react-router-dom": "~6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/client": "workspace:*",
|
"@tachybase/client": "workspace:*",
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"react-error-boundary": "^4.0.10",
|
"react-error-boundary": "^4.0.10",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-pdf": "^7.5.1",
|
"react-pdf": "^7.5.1",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.25.1",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"react-zoom-pan-pinch": "^3.4.4",
|
"react-zoom-pan-pinch": "^3.4.4",
|
||||||
"redis": "^4.6.11",
|
"redis": "^4.6.11",
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
"react": "~18.3.1",
|
"react": "~18.3.1",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.25.1",
|
||||||
"sequelize": "^6.26.0"
|
"sequelize": "^6.26.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.3.7",
|
"@ant-design/icons": "^5.3.7",
|
||||||
"antd-mobile": "^5.36.1",
|
"antd-mobile": "^5.36.1",
|
||||||
"react-router-dom": "^6.23.1"
|
"react-router-dom": "^6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/client": "workspace:*",
|
"@tachybase/client": "workspace:*",
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"antd": "5.19.1",
|
"antd": "5.19.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "^6.11.2"
|
"react-router-dom": "^6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/client": "workspace:*",
|
"@tachybase/client": "workspace:*",
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"@tachybase/schema": "workspace:*",
|
"@tachybase/schema": "workspace:*",
|
||||||
"antd": "5.19.1",
|
"antd": "5.19.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "^6.11.2"
|
"react-router-dom": "^6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/client": "workspace:*",
|
"@tachybase/client": "workspace:*",
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"cron": "^3.1.7",
|
"cron": "^3.1.7",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "^6.11.2"
|
"react-router-dom": "^6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/actions": "workspace:*",
|
"@tachybase/actions": "workspace:*",
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"@ant-design/icons": "~5.3.7",
|
"@ant-design/icons": "~5.3.7",
|
||||||
"antd": "5.19.1",
|
"antd": "5.19.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "6.x"
|
"react-router-dom": "~6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/actions": "workspace:*",
|
"@tachybase/actions": "workspace:*",
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"antd": "5.19.1",
|
"antd": "5.19.1",
|
||||||
"lodash": "~4.17.21",
|
"lodash": "~4.17.21",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "6.x"
|
"react-router-dom": "~6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/actions": "workspace:*",
|
"@tachybase/actions": "workspace:*",
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"antd": "5.19.1",
|
"antd": "5.19.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.25.1",
|
||||||
"sequelize": "^6.26.0"
|
"sequelize": "^6.26.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"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",
|
"lodash": "4.17.21",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "^6.11.2"
|
"react-router-dom": "^6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/client": "workspace:*",
|
"@tachybase/client": "workspace:*",
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"react": "~18.3.1",
|
"react": "~18.3.1",
|
||||||
"react-dom": "~18.3.1",
|
"react-dom": "~18.3.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "^6.11.2"
|
"react-router-dom": "^6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/actions": "workspace:*",
|
"@tachybase/actions": "workspace:*",
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"react": "~18.3.1",
|
"react": "~18.3.1",
|
||||||
"react-dom": "~18.3.1",
|
"react-dom": "~18.3.1",
|
||||||
"react-i18next": "~14.1.2",
|
"react-i18next": "~14.1.2",
|
||||||
"react-router-dom": "6.x"
|
"react-router-dom": "~6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/client": "workspace:*",
|
"@tachybase/client": "workspace:*",
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"pg": "^8.7.3",
|
"pg": "^8.7.3",
|
||||||
"react": "~18.3.1",
|
"react": "~18.3.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "^6.11.2"
|
"react-router-dom": "^6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/client": "workspace:*",
|
"@tachybase/client": "workspace:*",
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"openid-client": "^5.4.2",
|
"openid-client": "^5.4.2",
|
||||||
"react": "~18.3.1",
|
"react": "~18.3.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "^6.11.2"
|
"react-router-dom": "^6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/actions": "workspace:*",
|
"@tachybase/actions": "workspace:*",
|
||||||
|
@ -25,7 +25,7 @@ export class OidcPlugin extends Plugin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 注册接口
|
// 注册接口
|
||||||
this.app.resource({
|
this.app.resourcer.define({
|
||||||
name: 'oidc',
|
name: 'oidc',
|
||||||
actions: {
|
actions: {
|
||||||
getAuthUrl,
|
getAuthUrl,
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"antd": "5.19.1",
|
"antd": "5.19.1",
|
||||||
"react": "~18.3.1",
|
"react": "~18.3.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-router-dom": "^6.11.2"
|
"react-router-dom": "^6.25.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tachybase/actions": "workspace:*",
|
"@tachybase/actions": "workspace:*",
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
"react": "~18.3.1",
|
"react": "~18.3.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-js-cron": "^3.1.0",
|
"react-js-cron": "^3.1.0",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.25.1",
|
||||||
"sequelize": "^6.26.0"
|
"sequelize": "^6.26.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"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