feat: add ban user

you can see this action in admin-next
pull/90/head
moonrailgun 2 years ago
parent 75021144c3
commit ea3ad15f5f

@ -17,6 +17,7 @@ export interface UserBaseInfo {
avatar: string | null;
temporary: boolean;
emailVerified: boolean;
banned: boolean;
extra?: Record<string, unknown>;
}
@ -51,6 +52,7 @@ export function pickUserBaseInfo(userInfo: UserLoginInfo): UserBaseInfo {
'avatar',
'temporary',
'emailVerified',
'banned',
]);
}
@ -64,6 +66,7 @@ const builtinUserInfo: Record<string, () => UserBaseInfo> = {
avatar: null,
temporary: false,
emailVerified: false,
banned: false,
}),
};

@ -25,7 +25,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailchat-server-sdk": "workspace:^",
"tushan": "^0.2.12",
"tushan": "^0.2.16",
"vite-express": "0.8.0"
},
"devDependencies": {

@ -49,6 +49,7 @@ export const userFields = [
createAvatarField('avatar', {
preRenderTransform: parseUrlStr,
}),
createBooleanField('banned'),
createJSONField('settings', {
list: {
width: 200,

@ -12,6 +12,9 @@ export const i18n: TushanContextProps['i18n'] = {
custom: {
action: {
resetPassword: 'Reset Password',
banUser: 'Ban User',
banUserDesc:
'Banning a user disconnects the user from the current connection and prevents future logins',
},
dashboard: {
file: 'File',
@ -76,6 +79,7 @@ export const i18n: TushanContextProps['i18n'] = {
temporary: '是否游客',
type: '用户类型',
settings: '用户设置',
banned: '是否被封禁',
createdAt: '创建时间',
},
},
@ -147,6 +151,8 @@ export const i18n: TushanContextProps['i18n'] = {
custom: {
action: {
resetPassword: '重置密码',
banUser: '封禁用户',
banUserDesc: '封禁用户会将用户从当前连接断开并阻止之后的登录操作',
},
dashboard: {
file: '文件',

@ -4,16 +4,19 @@ import {
ListTable,
Message,
Modal,
useRefreshList,
useResourceContext,
useTranslation,
useUpdate,
} from 'tushan';
import { userFields } from '../fields';
import { request } from '../request';
export const UserList: React.FC = React.memo(() => {
const { t } = useTranslation();
const [update] = useUpdate();
const resource = useResourceContext();
const refreshUser = useRefreshList(resource);
return (
<ListTable
@ -28,16 +31,17 @@ export const UserList: React.FC = React.memo(() => {
detail: true,
edit: true,
delete: true,
refresh: true,
export: true,
custom: [
{
key: 'resetPassword',
label: t('custom.action.resetPassword'),
onClick: (record: any) => {
onClick: (record) => {
const { close } = Modal.confirm({
title: t('tushan.common.confirmTitle'),
content: t('tushan.common.confirmContent'),
onConfirm: async (e) => {
onConfirm: async () => {
try {
await update(resource, {
id: record.id,
@ -56,6 +60,30 @@ export const UserList: React.FC = React.memo(() => {
});
},
},
{
key: 'banUser',
label: t('custom.action.banUser'),
onClick: (record) => {
const { close } = Modal.confirm({
title: t('tushan.common.confirmTitle'),
content: t('custom.action.banUserDesc'),
onConfirm: async () => {
try {
await request.post('/user/ban', {
userId: record.id,
});
Message.success(t('tushan.common.success'));
refreshUser();
close();
} catch (err) {
console.error(err);
Message.error(String(err));
}
},
});
},
},
// TODO: unban
],
}}
/>

@ -10,7 +10,7 @@ export const broker = new TcBroker({
});
broker.start().then(() => {
console.log('Linked to Tailchat network, TRANSPORTER: ', transporter);
console.log('Connnected to Tailchat network, TRANSPORTER: ', transporter);
});
export function callBrokerAction<T>(

@ -1,6 +1,6 @@
import { Router } from 'express';
import jwt from 'jsonwebtoken';
import { callBrokerAction } from '../broker';
import { broker, callBrokerAction } from '../broker';
import { adminAuth, auth, authSecret } from '../middleware/auth';
import { configRouter } from './config';
import { networkRouter } from './network';
@ -99,6 +99,17 @@ router.get('/user/count/summary', auth(), async (req, res) => {
res.json({ summary });
});
router.post('/user/ban', auth(), async (req, res) => {
const { userId } = req.body;
const ret = await broker.call('user.banUser', {
userId,
});
res.json({
ret,
});
});
router.use(
'/users',
auth(),

@ -397,6 +397,28 @@ export const TcSocketIOService = (
},
},
/**
* userIdtoken
*/
getUserSocketToken: {
visibility: 'public',
params: {
userId: 'string',
},
async handler(
this: TcService,
ctx: TcContext<{ userId: string }>
): Promise<string[]> {
const userId = ctx.params.userId;
const io: SocketServer = this.io;
const remoteSockets = await io
.in(buildUserRoomId(userId))
.fetchSockets();
return remoteSockets.map((remoteSocket) => remoteSocket.data.token);
},
},
/**
*
*/

@ -7,11 +7,12 @@
"license": "Apache-2.0",
"private": true,
"scripts": {
"dev": "concurrently --kill-others npm:dev:main npm:dev:sdk npm:dev:plugins npm:dev:admin",
"dev": "concurrently --kill-others npm:dev:main npm:dev:sdk npm:dev:plugins npm:dev:admin-next",
"dev:main": "ts-node ./runner.ts",
"dev:sdk": "cd packages/sdk && pnpm watch",
"dev:plugins": "pnpm run --filter \"./plugins/*\" build:web:watch",
"dev:admin": "cd admin && pnpm dev",
"dev:admin-next": "cd admin-next && pnpm dev",
"debug": "node --inspect -r ts-node/register ./runner.ts",
"build": "ts-node scripts/build.ts",
"start:service": "cd dist && tailchat-runner --config moleculer.config.js",

@ -40,7 +40,12 @@ export class NoPermissionError extends TcError {
export class BannedError extends TcError {
constructor(message?: string, code?: number, type?: string, data?: unknown) {
super(message ?? 'You has been banned', code ?? 403, type, data);
super(
message ?? 'You has been banned',
code ?? 403,
type ?? 'banned',
data
);
}
}

@ -20,7 +20,7 @@ import {
EntityError,
db,
call,
NoPermissionError,
BannedError,
} from 'tailchat-server-sdk';
import {
generateRandomNumStr,
@ -56,6 +56,7 @@ class UserService extends TcService {
'avatar',
'type',
'emailVerified',
'banned',
'extra',
'createdAt',
]);
@ -144,6 +145,12 @@ class UserService extends TcService {
token: 'string',
},
});
this.registerAction('banUser', this.banUser, {
params: {
userId: 'string',
},
visibility: 'public',
});
this.registerAction('whoami', this.whoami);
this.registerAction(
'searchUserWithUniqueName',
@ -291,7 +298,7 @@ class UserService extends TcService {
}
if (user.banned === true) {
throw new NoPermissionError(t('用户被封禁'), 403);
throw new BannedError(t('用户被封禁'), 403);
}
// Transform user entity (remove password and all protected fields)
@ -646,11 +653,11 @@ class UserService extends TcService {
// token 中没有 _id
throw new EntityError(t('Token 内容不正确'));
}
const doc = await this.getById(decoded._id);
const doc = await this.adapter.model.findById(decoded._id);
const user: User = await this.transformDocuments(ctx, {}, doc);
if (user.banned === true) {
throw new NoPermissionError(t('用户被封禁'));
throw new BannedError(t('用户被封禁'));
}
const json = await this.transformEntity(user, true, ctx.meta.token);
@ -670,6 +677,36 @@ class UserService extends TcService {
}
}
/**
*
*/
async banUser(
ctx: TcContext<{
userId: string;
}>
) {
const { userId } = ctx.params;
await this.adapter.model.updateOne(
{
_id: userId,
},
{
banned: true,
}
);
this.cleanUserInfoCache(userId);
const tokens = await ctx.call('gateway.getUserSocketToken', {
userId,
});
if (Array.isArray(tokens)) {
tokens.map((token) => this.cleanActionCache('resolveToken', [token]));
}
await ctx.call('gateway.tickUser', {
userId,
});
}
async whoami(ctx: TcContext) {
return ctx.meta ?? null;
}

Loading…
Cancel
Save