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') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 为默认角色添加权限组
|
||||
const User = this.db.getCollection('users');
|
||||
await User.repository.update({
|
||||
filter: {
|
||||
id: 1,
|
||||
},
|
||||
values: {
|
||||
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
|
||||
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:
|
||||
dependencies:
|
||||
'@tachybase/actions':
|
||||
|
Loading…
Reference in New Issue
Block a user