feat: share (#1358)
Co-authored-by: sealday <zhanglin@daoyoucloud.com> Reviewed-on: daoyoucloud/tachybase#1358 Reviewed-by: sealday <zhanglin@daoyoucloud.com> Co-authored-by: TomyJan <TomyJan6@gmail.com> Co-committed-by: TomyJan <TomyJan6@gmail.com>
This commit is contained in:
parent
168eccbdf1
commit
a5c4b68c16
@ -412,8 +412,13 @@ export class PluginACL extends Plugin {
|
|||||||
if (plugin.getName() !== 'users') {
|
if (plugin.getName() !== 'users') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 为默认角色添加权限组
|
||||||
const User = this.db.getCollection('users');
|
const User = this.db.getCollection('users');
|
||||||
await User.repository.update({
|
await User.repository.update({
|
||||||
|
filter: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
values: {
|
values: {
|
||||||
roles: ['root', 'admin', 'member'],
|
roles: ['root', 'admin', 'member'],
|
||||||
},
|
},
|
||||||
|
2
packages/plugins/@tachybase/plugin-share/.npmignore
Normal file
2
packages/plugins/@tachybase/plugin-share/.npmignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/node_modules
|
||||||
|
/src
|
14
packages/plugins/@tachybase/plugin-share/README.md
Normal file
14
packages/plugins/@tachybase/plugin-share/README.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# share
|
||||||
|
|
||||||
|
English | [中文](./README.zh-CN.md)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```
|
||||||
|
pnpm pm add @tachybase/plugin-share
|
||||||
|
pnpm pm enable @tachybase/plugin-share
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO
|
14
packages/plugins/@tachybase/plugin-share/README.zh-CN.md
Normal file
14
packages/plugins/@tachybase/plugin-share/README.zh-CN.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# share
|
||||||
|
|
||||||
|
[English](./README.md) | 中文
|
||||||
|
|
||||||
|
## 安装激活
|
||||||
|
|
||||||
|
```
|
||||||
|
pnpm pm add @tachybase/plugin-share
|
||||||
|
pnpm pm enable @tachybase/plugin-share
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
TODO
|
2
packages/plugins/@tachybase/plugin-share/client.d.ts
vendored
Normal file
2
packages/plugins/@tachybase/plugin-share/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './dist/client';
|
||||||
|
export { default } from './dist/client';
|
1
packages/plugins/@tachybase/plugin-share/client.js
Normal file
1
packages/plugins/@tachybase/plugin-share/client.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./dist/client/index.js');
|
23
packages/plugins/@tachybase/plugin-share/package.json
Normal file
23
packages/plugins/@tachybase/plugin-share/package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "@tachybase/plugin-share",
|
||||||
|
"displayName": "Share Externally",
|
||||||
|
"version": "0.21.73",
|
||||||
|
"description": "Share pages with external users.",
|
||||||
|
"keywords": [
|
||||||
|
"Users & permissions"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"main": "dist/server/index.js",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tachybase/actions": "workspace:*",
|
||||||
|
"@tachybase/auth": "workspace:*",
|
||||||
|
"@tachybase/client": "workspace:*",
|
||||||
|
"@tachybase/database": "workspace:*",
|
||||||
|
"@tachybase/server": "workspace:*",
|
||||||
|
"@tachybase/test": "workspace:*"
|
||||||
|
},
|
||||||
|
"description.zh-CN": "与外部用户分享页面。",
|
||||||
|
"displayName.zh-CN": "对外分享"
|
||||||
|
}
|
2
packages/plugins/@tachybase/plugin-share/server.d.ts
vendored
Normal file
2
packages/plugins/@tachybase/plugin-share/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './dist/server';
|
||||||
|
export { default } from './dist/server';
|
1
packages/plugins/@tachybase/plugin-share/server.js
Normal file
1
packages/plugins/@tachybase/plugin-share/server.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./dist/server/index.js');
|
@ -0,0 +1,5 @@
|
|||||||
|
import { Plugin } from '@tachybase/client';
|
||||||
|
|
||||||
|
export class PluginShareClient extends Plugin {}
|
||||||
|
|
||||||
|
export default PluginShareClient;
|
@ -0,0 +1,3 @@
|
|||||||
|
import { name } from '../package.json';
|
||||||
|
|
||||||
|
export const NAMESPACE = name;
|
2
packages/plugins/@tachybase/plugin-share/src/index.ts
Normal file
2
packages/plugins/@tachybase/plugin-share/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './server';
|
||||||
|
export { default } from './server';
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"Guest": "Guest",
|
||||||
|
"Guest user not found.": "Guest user not found.",
|
||||||
|
"Not allowed to modify login information of guest user.": "Not allowed to modify login information of guest user.",
|
||||||
|
"Unauthorized": "Unauthorized"
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"Guest": "访客",
|
||||||
|
"Guest user not found.": "访客用户未找到。",
|
||||||
|
"Not allowed to modify login information of guest user.": "不允许修改访客用户的登录信息。",
|
||||||
|
"Unauthorized": "未授权"
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
import { Context, Next } from '@tachybase/actions';
|
||||||
|
|
||||||
|
import { NAMESPACE } from '../../constants';
|
||||||
|
|
||||||
|
export async function update(ctx: Context, next: Next) {
|
||||||
|
const { values } = ctx.action.params;
|
||||||
|
const targetUserId = Number(ctx.request.url.split('filterByTk=')[1]);
|
||||||
|
const guestUser = await ctx.db.getRepository('users').findOne({
|
||||||
|
filter: {
|
||||||
|
username: 'guest',
|
||||||
|
},
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!guestUser) {
|
||||||
|
ctx.throw(401, ctx.t('Guest user not found.', { ns: NAMESPACE }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只拦截 update guest 用户的请求
|
||||||
|
if (targetUserId === guestUser.id) {
|
||||||
|
if (
|
||||||
|
(values.username && values.username !== guestUser.username) ||
|
||||||
|
(values.email && values.email !== guestUser.email) ||
|
||||||
|
values.password
|
||||||
|
) {
|
||||||
|
ctx.throw(401, ctx.t('Not allowed to modify login information of guest user.', { ns: NAMESPACE }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserRepo = ctx.db.getRepository('users');
|
||||||
|
const result = await UserRepo.update({
|
||||||
|
filterByTk: targetUserId,
|
||||||
|
values,
|
||||||
|
});
|
||||||
|
ctx.body = result;
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProfile(ctx: Context, next: Next) {
|
||||||
|
const { values } = ctx.action.params;
|
||||||
|
const { currentUser } = ctx.state;
|
||||||
|
if (!currentUser || currentUser.username === 'guest' || currentUser.email === 'guest@tachybase.com') {
|
||||||
|
ctx.throw(401, ctx.t('Not allowed to modify login information of guest user.', { ns: NAMESPACE }));
|
||||||
|
}
|
||||||
|
const UserRepo = ctx.db.getRepository('users');
|
||||||
|
const result = await UserRepo.update({
|
||||||
|
filterByTk: currentUser.id,
|
||||||
|
values,
|
||||||
|
});
|
||||||
|
ctx.body = result;
|
||||||
|
await next();
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './plugin';
|
@ -0,0 +1,35 @@
|
|||||||
|
import { Context } from '@tachybase/actions';
|
||||||
|
|
||||||
|
import { NAMESPACE } from '../../constants';
|
||||||
|
|
||||||
|
// 禁止访客进行任何鉴权操作,除了 check 和 signOut
|
||||||
|
function banGuestActionMiddleware() {
|
||||||
|
return async (ctx: Context, next) => {
|
||||||
|
// 注意由于已经注入访客 token, 还需要判断请求体里面用户是否为 guest
|
||||||
|
const reqBody = ctx.request.body;
|
||||||
|
if (
|
||||||
|
ctx.action.resourceName === 'auth' &&
|
||||||
|
!['check', 'signOut'].includes(ctx.action.actionName) &&
|
||||||
|
ctx.auth.user &&
|
||||||
|
(reqBody.account === 'guest' || reqBody.account === 'guest@tachybase.com')
|
||||||
|
) {
|
||||||
|
const { username, email } = ctx.auth.user;
|
||||||
|
if (username === 'guest' || email === 'guest@tachybase.com') {
|
||||||
|
ctx.withoutDataWrapping = true;
|
||||||
|
ctx.status = 401;
|
||||||
|
ctx.body = {
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
message: ctx.t('Unauthorized', { ns: NAMESPACE }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { banGuestActionMiddleware };
|
130
packages/plugins/@tachybase/plugin-share/src/server/plugin.ts
Normal file
130
packages/plugins/@tachybase/plugin-share/src/server/plugin.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { Context } from '@tachybase/actions';
|
||||||
|
import { BaseAuth } from '@tachybase/auth';
|
||||||
|
import { Plugin } from '@tachybase/server';
|
||||||
|
|
||||||
|
import * as actions from './actions/users';
|
||||||
|
import { banGuestActionMiddleware } from './middlewares/ban-guest-action';
|
||||||
|
|
||||||
|
export class PluginShareServer extends Plugin {
|
||||||
|
async beforeLoad() {
|
||||||
|
for (const [key, action] of Object.entries(actions)) {
|
||||||
|
this.app.resourcer.registerActionHandler(`users:${key}`, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
const banGuestAction = banGuestActionMiddleware();
|
||||||
|
|
||||||
|
this.app.use(
|
||||||
|
async (ctx: Context, next) => {
|
||||||
|
try {
|
||||||
|
await banGuestAction(ctx, next);
|
||||||
|
} catch (error) {
|
||||||
|
ctx.logger.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ after: 'restApi', group: 'after' },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.app.acl.addFixedParams('users', 'destroy', () => {
|
||||||
|
return {
|
||||||
|
filter: {
|
||||||
|
'username.$ne': 'guest',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
BaseAuth.prototype.check = async function () {
|
||||||
|
let token = this.ctx.getBearerToken();
|
||||||
|
if (!token) {
|
||||||
|
// 注入访客用户 token, TODO: 也许有更好的方法来拦截 BaseAuth 的逻辑
|
||||||
|
// TODO: 访客模式启用判断, 访客 token 有效期设置, 带指定参数才可访客登录,前端导航栏头像适配访客
|
||||||
|
const user = await this.userRepository.findOne({
|
||||||
|
filter: {
|
||||||
|
username: 'guest',
|
||||||
|
},
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
if (!user) {
|
||||||
|
this.ctx.logger.error('guest mode enabled, but no guest user in database', { method: 'check' });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
token = this.jwt.sign({
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { userId, roleName } = await this.jwt.decode(token);
|
||||||
|
|
||||||
|
if (roleName) {
|
||||||
|
this.ctx.headers['x-role'] = roleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cache = this.ctx.cache;
|
||||||
|
return await cache.wrap(this.getCacheKey(userId), () =>
|
||||||
|
this.userRepository.findOne({
|
||||||
|
filter: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
raw: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
this.ctx.logger.error(err, { method: 'check' });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstallingData(options: any = {}) {
|
||||||
|
const {
|
||||||
|
guestEmail = 'guest@tachybase.com',
|
||||||
|
guestPassword = 'N0_PAS5W0RD',
|
||||||
|
guestNickname = 'Guest',
|
||||||
|
guestUsername = 'guest',
|
||||||
|
} = options.users || options?.cliArgs?.[0] || {};
|
||||||
|
return { guestEmail, guestPassword, guestNickname, guestUsername };
|
||||||
|
}
|
||||||
|
|
||||||
|
async install(options) {
|
||||||
|
const { guestNickname, guestPassword, guestEmail, guestUsername } = this.getInstallingData(options);
|
||||||
|
const User = this.db.getCollection('users');
|
||||||
|
if (await User.repository.findOne({ filter: { email: guestEmail } })) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await User.repository.create({
|
||||||
|
values: {
|
||||||
|
email: guestEmail,
|
||||||
|
password: guestPassword,
|
||||||
|
nickname: guestNickname,
|
||||||
|
username: guestUsername,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const roles = this.db.getCollection('roles');
|
||||||
|
await roles.repository.createMany({
|
||||||
|
records: [
|
||||||
|
{
|
||||||
|
name: 'guest',
|
||||||
|
title: '{{t("Guest")}}',
|
||||||
|
allowConfigure: false,
|
||||||
|
allowNewMenu: false,
|
||||||
|
snippets: ['!ui.*', '!pm', '!pm.*'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await User.repository.update({
|
||||||
|
filter: {
|
||||||
|
username: 'guest',
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
roles: ['guest'],
|
||||||
|
},
|
||||||
|
forceUpdate: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginShareServer;
|
@ -3645,6 +3645,27 @@ importers:
|
|||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.2.0(antd@5.19.1)(react-dom@18.3.1)(react@18.3.1)
|
version: 3.2.0(antd@5.19.1)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
|
||||||
|
packages/plugins/@tachybase/plugin-share:
|
||||||
|
dependencies:
|
||||||
|
'@tachybase/actions':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../core/actions
|
||||||
|
'@tachybase/auth':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../core/auth
|
||||||
|
'@tachybase/client':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../core/client
|
||||||
|
'@tachybase/database':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../core/database
|
||||||
|
'@tachybase/server':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../core/server
|
||||||
|
'@tachybase/test':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../core/test
|
||||||
|
|
||||||
packages/plugins/@tachybase/plugin-sms-auth:
|
packages/plugins/@tachybase/plugin-sms-auth:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tachybase/actions':
|
'@tachybase/actions':
|
||||||
|
Loading…
Reference in New Issue
Block a user