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:
TomyJan 2024-07-22 13:47:41 +08:00 committed by sealday
parent 168eccbdf1
commit a5c4b68c16
22 changed files with 328 additions and 3 deletions

View File

@ -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'],
},

View File

@ -0,0 +1,2 @@
/node_modules
/src

View 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

View File

@ -0,0 +1,14 @@
# share
[English](./README.md) | 中文
## 安装激活
```
pnpm pm add @tachybase/plugin-share
pnpm pm enable @tachybase/plugin-share
```
## 使用方法
TODO

View File

@ -0,0 +1,2 @@
export * from './dist/client';
export { default } from './dist/client';

View File

@ -0,0 +1 @@
module.exports = require('./dist/client/index.js');

View 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": "对外分享"
}

View File

@ -0,0 +1,2 @@
export * from './dist/server';
export { default } from './dist/server';

View File

@ -0,0 +1 @@
module.exports = require('./dist/server/index.js');

View File

@ -0,0 +1,5 @@
import { Plugin } from '@tachybase/client';
export class PluginShareClient extends Plugin {}
export default PluginShareClient;

View File

@ -0,0 +1,3 @@
import { name } from '../package.json';
export const NAMESPACE = name;

View File

@ -0,0 +1,2 @@
export * from './server';
export { default } from './server';

View File

@ -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"
}

View File

@ -0,0 +1,6 @@
{
"Guest": "访客",
"Guest user not found.": "访客用户未找到。",
"Not allowed to modify login information of guest user.": "不允许修改访客用户的登录信息。",
"Unauthorized": "未授权"
}

View File

@ -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();
}

View File

@ -0,0 +1 @@
export { default } from './plugin';

View File

@ -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 };

View 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;

View File

@ -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':