mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-21 11:43:56 +00:00
21
docSite/content/zh-cn/docs/development/upgrading/499.md
Normal file
21
docSite/content/zh-cn/docs/development/upgrading/499.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
title: 'V4.9.9(进行中)'
|
||||||
|
description: 'FastGPT V4.9.9 更新说明'
|
||||||
|
icon: 'upgrade'
|
||||||
|
draft: false
|
||||||
|
toc: true
|
||||||
|
weight: 791
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## 🚀 新增内容
|
||||||
|
|
||||||
|
1. 切换 SessionId 来替代 JWT 实现登录鉴权,可控制最大登录客户端数量。
|
||||||
|
|
||||||
|
## ⚙️ 优化
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 🐛 修复
|
||||||
|
|
||||||
|
|
2
env.d.ts
vendored
2
env.d.ts
vendored
@@ -4,7 +4,6 @@ declare global {
|
|||||||
LOG_DEPTH: string;
|
LOG_DEPTH: string;
|
||||||
DEFAULT_ROOT_PSW: string;
|
DEFAULT_ROOT_PSW: string;
|
||||||
DB_MAX_LINK: string;
|
DB_MAX_LINK: string;
|
||||||
TOKEN_KEY: string;
|
|
||||||
FILE_TOKEN_KEY: string;
|
FILE_TOKEN_KEY: string;
|
||||||
ROOT_KEY: string;
|
ROOT_KEY: string;
|
||||||
OPENAI_BASE_URL: string;
|
OPENAI_BASE_URL: string;
|
||||||
@@ -37,6 +36,7 @@ declare global {
|
|||||||
CONFIG_JSON_PATH?: string;
|
CONFIG_JSON_PATH?: string;
|
||||||
PASSWORD_LOGIN_LOCK_SECONDS?: string;
|
PASSWORD_LOGIN_LOCK_SECONDS?: string;
|
||||||
PASSWORD_EXPIRED_MONTH?: string;
|
PASSWORD_EXPIRED_MONTH?: string;
|
||||||
|
MAX_LOGIN_SESSION?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { getGlobalRedisCacheConnection } from './index';
|
import { getGlobalRedisConnection } from './index';
|
||||||
import { addLog } from '../system/log';
|
import { addLog } from '../system/log';
|
||||||
import { retryFn } from '@fastgpt/global/common/system/utils';
|
import { retryFn } from '@fastgpt/global/common/system/utils';
|
||||||
|
|
||||||
|
const redisPrefix = 'cache:';
|
||||||
|
const getCacheKey = (key: string) => `${redisPrefix}${key}`;
|
||||||
|
|
||||||
export enum CacheKeyEnum {
|
export enum CacheKeyEnum {
|
||||||
team_vector_count = 'team_vector_count'
|
team_vector_count = 'team_vector_count'
|
||||||
}
|
}
|
||||||
@@ -13,12 +16,12 @@ export const setRedisCache = async (
|
|||||||
) => {
|
) => {
|
||||||
return await retryFn(async () => {
|
return await retryFn(async () => {
|
||||||
try {
|
try {
|
||||||
const redis = getGlobalRedisCacheConnection();
|
const redis = getGlobalRedisConnection();
|
||||||
|
|
||||||
if (expireSeconds) {
|
if (expireSeconds) {
|
||||||
await redis.set(key, data, 'EX', expireSeconds);
|
await redis.set(getCacheKey(key), data, 'EX', expireSeconds);
|
||||||
} else {
|
} else {
|
||||||
await redis.set(key, data);
|
await redis.set(getCacheKey(key), data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addLog.error('Set cache error:', error);
|
addLog.error('Set cache error:', error);
|
||||||
@@ -28,11 +31,11 @@ export const setRedisCache = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getRedisCache = async (key: string) => {
|
export const getRedisCache = async (key: string) => {
|
||||||
const redis = getGlobalRedisCacheConnection();
|
const redis = getGlobalRedisConnection();
|
||||||
return await retryFn(() => redis.get(key));
|
return await retryFn(() => redis.get(getCacheKey(key)));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const delRedisCache = async (key: string) => {
|
export const delRedisCache = async (key: string) => {
|
||||||
const redis = getGlobalRedisCacheConnection();
|
const redis = getGlobalRedisConnection();
|
||||||
await retryFn(() => redis.del(key));
|
await retryFn(() => redis.del(getCacheKey(key)));
|
||||||
};
|
};
|
||||||
|
@@ -27,17 +27,26 @@ export const newWorkerRedisConnection = () => {
|
|||||||
return redis;
|
return redis;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getGlobalRedisCacheConnection = () => {
|
export const FASTGPT_REDIS_PREFIX = 'fastgpt:';
|
||||||
if (global.redisCache) return global.redisCache;
|
export const getGlobalRedisConnection = () => {
|
||||||
|
if (global.redisClient) return global.redisClient;
|
||||||
|
|
||||||
global.redisCache = new Redis(REDIS_URL, { keyPrefix: 'fastgpt:cache:' });
|
global.redisClient = new Redis(REDIS_URL, { keyPrefix: FASTGPT_REDIS_PREFIX });
|
||||||
|
|
||||||
global.redisCache.on('connect', () => {
|
global.redisClient.on('connect', () => {
|
||||||
addLog.info('Redis connected');
|
addLog.info('Redis connected');
|
||||||
});
|
});
|
||||||
global.redisCache.on('error', (error) => {
|
global.redisClient.on('error', (error) => {
|
||||||
addLog.error('Redis connection error', error);
|
addLog.error('Redis connection error', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
return global.redisCache;
|
return global.redisClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAllKeysByPrefix = async (key: string) => {
|
||||||
|
const redis = getGlobalRedisConnection();
|
||||||
|
const keys = (await redis.keys(`${FASTGPT_REDIS_PREFIX}${key}:*`)).map((key) =>
|
||||||
|
key.replace(FASTGPT_REDIS_PREFIX, '')
|
||||||
|
);
|
||||||
|
return keys;
|
||||||
};
|
};
|
||||||
|
2
packages/service/common/redis/type.d.ts
vendored
2
packages/service/common/redis/type.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
import type Redis from 'ioredis';
|
import type Redis from 'ioredis';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var redisCache: Redis | null;
|
var redisClient: Redis | null;
|
||||||
}
|
}
|
||||||
|
@@ -135,8 +135,8 @@ export const llmStreamResponseToAnswerText = async (
|
|||||||
|
|
||||||
// Tool calls
|
// Tool calls
|
||||||
if (responseChoice?.tool_calls?.length) {
|
if (responseChoice?.tool_calls?.length) {
|
||||||
responseChoice.tool_calls.forEach((toolCall) => {
|
responseChoice.tool_calls.forEach((toolCall, i) => {
|
||||||
const index = toolCall.index;
|
const index = toolCall.index ?? i;
|
||||||
|
|
||||||
if (toolCall.id || callingTool) {
|
if (toolCall.id || callingTool) {
|
||||||
// 有 id,代表新 call 工具
|
// 有 id,代表新 call 工具
|
||||||
|
@@ -20,6 +20,7 @@ import { type MemberGroupSchemaType } from '@fastgpt/global/support/permission/m
|
|||||||
import { type TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
|
import { type TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
|
||||||
import { type OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
|
import { type OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
import { getOrgIdSetWithParentByTmbId } from './org/controllers';
|
import { getOrgIdSetWithParentByTmbId } from './org/controllers';
|
||||||
|
import { authUserSession } from '../user/session';
|
||||||
|
|
||||||
/** get resource permission for a team member
|
/** get resource permission for a team member
|
||||||
* If there is no permission for the team member, it will return undefined
|
* If there is no permission for the team member, it will return undefined
|
||||||
@@ -213,51 +214,6 @@ export const delResourcePermission = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* 下面代码等迁移 */
|
/* 下面代码等迁移 */
|
||||||
/* create token */
|
|
||||||
export function createJWT(user: {
|
|
||||||
_id?: string;
|
|
||||||
team?: { teamId?: string; tmbId: string };
|
|
||||||
isRoot?: boolean;
|
|
||||||
}) {
|
|
||||||
const key = process.env.TOKEN_KEY as string;
|
|
||||||
const token = jwt.sign(
|
|
||||||
{
|
|
||||||
userId: String(user._id),
|
|
||||||
teamId: String(user.team?.teamId),
|
|
||||||
tmbId: String(user.team?.tmbId),
|
|
||||||
isRoot: user.isRoot,
|
|
||||||
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7
|
|
||||||
},
|
|
||||||
key
|
|
||||||
);
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
// auth token
|
|
||||||
export function authJWT(token: string) {
|
|
||||||
return new Promise<{
|
|
||||||
userId: string;
|
|
||||||
teamId: string;
|
|
||||||
tmbId: string;
|
|
||||||
isRoot: boolean;
|
|
||||||
}>((resolve, reject) => {
|
|
||||||
const key = process.env.TOKEN_KEY as string;
|
|
||||||
|
|
||||||
jwt.verify(token, key, (err, decoded: any) => {
|
|
||||||
if (err || !decoded?.userId) {
|
|
||||||
reject(ERROR_ENUM.unAuthorization);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
userId: decoded.userId,
|
|
||||||
teamId: decoded.teamId || '',
|
|
||||||
tmbId: decoded.tmbId,
|
|
||||||
isRoot: decoded.isRoot
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function parseHeaderCert({
|
export async function parseHeaderCert({
|
||||||
req,
|
req,
|
||||||
@@ -275,7 +231,7 @@ export async function parseHeaderCert({
|
|||||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await authJWT(cookieToken);
|
return authUserSession(cookieToken);
|
||||||
}
|
}
|
||||||
// from authorization get apikey
|
// from authorization get apikey
|
||||||
async function parseAuthorization(authorization?: string) {
|
async function parseAuthorization(authorization?: string) {
|
||||||
@@ -345,6 +301,7 @@ export async function parseHeaderCert({
|
|||||||
if (authToken && (token || cookie)) {
|
if (authToken && (token || cookie)) {
|
||||||
// user token(from fastgpt web)
|
// user token(from fastgpt web)
|
||||||
const res = await authCookieToken(cookie, token);
|
const res = await authCookieToken(cookie, token);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: res.userId,
|
uid: res.userId,
|
||||||
teamId: res.teamId,
|
teamId: res.teamId,
|
||||||
|
179
packages/service/support/user/session.ts
Normal file
179
packages/service/support/user/session.ts
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
import { retryFn } from '@fastgpt/global/common/system/utils';
|
||||||
|
import { getAllKeysByPrefix, getGlobalRedisConnection } from '../../common/redis';
|
||||||
|
import { addLog } from '../../common/system/log';
|
||||||
|
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
|
||||||
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
|
|
||||||
|
const redisPrefix = 'session:';
|
||||||
|
const getSessionKey = (key: string) => `${redisPrefix}${key}`;
|
||||||
|
|
||||||
|
type SessionType = {
|
||||||
|
userId: string;
|
||||||
|
teamId: string;
|
||||||
|
tmbId: string;
|
||||||
|
isRoot?: boolean;
|
||||||
|
createdAt: number;
|
||||||
|
ip?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Session manager */
|
||||||
|
const setSession = async ({
|
||||||
|
key,
|
||||||
|
data,
|
||||||
|
expireSeconds
|
||||||
|
}: {
|
||||||
|
key: string;
|
||||||
|
data: SessionType;
|
||||||
|
expireSeconds: number;
|
||||||
|
}) => {
|
||||||
|
return await retryFn(async () => {
|
||||||
|
try {
|
||||||
|
const redis = getGlobalRedisConnection();
|
||||||
|
const formatKey = getSessionKey(key);
|
||||||
|
|
||||||
|
// 使用 hmset 存储对象字段
|
||||||
|
await redis.hmset(formatKey, {
|
||||||
|
userId: data.userId,
|
||||||
|
teamId: data.teamId,
|
||||||
|
tmbId: data.tmbId,
|
||||||
|
isRoot: data.isRoot ? '1' : '0',
|
||||||
|
createdAt: data.createdAt.toString(),
|
||||||
|
ip: data.ip
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置过期时间
|
||||||
|
if (expireSeconds) {
|
||||||
|
await redis.expire(formatKey, expireSeconds);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
addLog.error('Set session error:', error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const delSession = (key: string) => {
|
||||||
|
const redis = getGlobalRedisConnection();
|
||||||
|
retryFn(() => redis.del(getSessionKey(key)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSession = async (key: string): Promise<SessionType> => {
|
||||||
|
const formatKey = getSessionKey(key);
|
||||||
|
const redis = getGlobalRedisConnection();
|
||||||
|
|
||||||
|
// 使用 hgetall 获取所有字段
|
||||||
|
const data = await retryFn(() => redis.hgetall(formatKey));
|
||||||
|
|
||||||
|
if (!data || Object.keys(data).length === 0) {
|
||||||
|
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
userId: data.userId,
|
||||||
|
teamId: data.teamId,
|
||||||
|
tmbId: data.tmbId,
|
||||||
|
isRoot: data.isRoot === '1',
|
||||||
|
createdAt: parseInt(data.createdAt),
|
||||||
|
ip: data.ip
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
addLog.error('Parse session error:', error);
|
||||||
|
delSession(formatKey);
|
||||||
|
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const delUserAllSession = async (userId: string, whileList?: string[]) => {
|
||||||
|
const formatWhileList = whileList?.map((item) => getSessionKey(item));
|
||||||
|
const redis = getGlobalRedisConnection();
|
||||||
|
const keys = (await getAllKeysByPrefix(`${redisPrefix}${userId}`)).filter(
|
||||||
|
(item) => !formatWhileList?.includes(item)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (keys.length > 0) {
|
||||||
|
await redis.del(keys);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 会根据创建时间,删除超出客户端登录限制的 session
|
||||||
|
const delRedundantSession = async (userId: string) => {
|
||||||
|
// 至少为 1,默认为 10
|
||||||
|
let maxSession = process.env.MAX_LOGIN_SESSION ? Number(process.env.MAX_LOGIN_SESSION) : 10;
|
||||||
|
if (maxSession < 1) {
|
||||||
|
maxSession = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const redis = getGlobalRedisConnection();
|
||||||
|
const keys = await getAllKeysByPrefix(`${redisPrefix}${userId}`);
|
||||||
|
|
||||||
|
if (keys.length <= maxSession) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有会话的创建时间
|
||||||
|
const sessionList = await Promise.all(
|
||||||
|
keys.map(async (key) => {
|
||||||
|
try {
|
||||||
|
const data = await redis.hgetall(key);
|
||||||
|
if (!data || Object.keys(data).length === 0) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
createdAt: parseInt(data.createdAt)
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 过滤掉无效数据并按创建时间排序
|
||||||
|
const validSessions = sessionList.filter(Boolean) as { key: string; createdAt: number }[];
|
||||||
|
|
||||||
|
validSessions.sort((a, b) => a.createdAt - b.createdAt);
|
||||||
|
|
||||||
|
// 删除最早创建的会话
|
||||||
|
const delKeys = validSessions.slice(0, validSessions.length - maxSession).map((item) => item.key);
|
||||||
|
|
||||||
|
if (delKeys.length > 0) {
|
||||||
|
await redis.del(delKeys);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const createUserSession = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
tmbId,
|
||||||
|
isRoot,
|
||||||
|
ip
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
teamId: string;
|
||||||
|
tmbId: string;
|
||||||
|
isRoot?: boolean;
|
||||||
|
ip?: string | null;
|
||||||
|
}) => {
|
||||||
|
const key = `${String(userId)}:${getNanoid(32)}`;
|
||||||
|
|
||||||
|
await setSession({
|
||||||
|
key,
|
||||||
|
data: {
|
||||||
|
userId: String(userId),
|
||||||
|
teamId: String(teamId),
|
||||||
|
tmbId: String(tmbId),
|
||||||
|
isRoot,
|
||||||
|
createdAt: new Date().getTime(),
|
||||||
|
ip
|
||||||
|
},
|
||||||
|
expireSeconds: 7 * 24 * 60 * 60
|
||||||
|
});
|
||||||
|
|
||||||
|
delRedundantSession(userId);
|
||||||
|
|
||||||
|
return key;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authUserSession = async (key: string): Promise<SessionType> => {
|
||||||
|
const data = await getSession(key);
|
||||||
|
return data;
|
||||||
|
};
|
@@ -3,8 +3,6 @@ LOG_DEPTH=3
|
|||||||
DEFAULT_ROOT_PSW=123456
|
DEFAULT_ROOT_PSW=123456
|
||||||
# 数据库最大连接数
|
# 数据库最大连接数
|
||||||
DB_MAX_LINK=5
|
DB_MAX_LINK=5
|
||||||
# token
|
|
||||||
TOKEN_KEY=dfdasfdas
|
|
||||||
# 文件阅读时的密钥
|
# 文件阅读时的密钥
|
||||||
FILE_TOKEN_KEY=filetokenkey
|
FILE_TOKEN_KEY=filetokenkey
|
||||||
# root key, 最高权限
|
# root key, 最高权限
|
||||||
@@ -65,6 +63,8 @@ CHECK_INTERNAL_IP=false
|
|||||||
PASSWORD_LOGIN_LOCK_SECONDS=
|
PASSWORD_LOGIN_LOCK_SECONDS=
|
||||||
# 密码过期月份,不设置则不会过期
|
# 密码过期月份,不设置则不会过期
|
||||||
PASSWORD_EXPIRED_MONTH=
|
PASSWORD_EXPIRED_MONTH=
|
||||||
|
# 最大登录客户端数量,默认为 10
|
||||||
|
MAX_LOGIN_SESSION=
|
||||||
|
|
||||||
# 特殊配置
|
# 特殊配置
|
||||||
# 自定义跨域,不配置时,默认都允许跨域(逗号分割)
|
# 自定义跨域,不配置时,默认都允许跨域(逗号分割)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||||
import { createJWT, setCookie } from '@fastgpt/service/support/permission/controller';
|
import { setCookie } from '@fastgpt/service/support/permission/controller';
|
||||||
import { getUserDetail } from '@fastgpt/service/support/user/controller';
|
import { getUserDetail } from '@fastgpt/service/support/user/controller';
|
||||||
import type { PostLoginProps } from '@fastgpt/global/support/user/api.d';
|
import type { PostLoginProps } from '@fastgpt/global/support/user/api.d';
|
||||||
import { UserStatusEnum } from '@fastgpt/global/support/user/constant';
|
import { UserStatusEnum } from '@fastgpt/global/support/user/constant';
|
||||||
@@ -13,6 +13,8 @@ import { addOperationLog } from '@fastgpt/service/support/operationLog/addOperat
|
|||||||
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
|
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
|
||||||
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
|
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
|
||||||
import { authCode } from '@fastgpt/service/support/user/auth/controller';
|
import { authCode } from '@fastgpt/service/support/user/auth/controller';
|
||||||
|
import { createUserSession } from '@fastgpt/service/support/user/session';
|
||||||
|
import requestIp from 'request-ip';
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const { username, password, code } = req.body as PostLoginProps;
|
const { username, password, code } = req.body as PostLoginProps;
|
||||||
@@ -61,20 +63,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
lastLoginTmbId: userDetail.team.tmbId
|
lastLoginTmbId: userDetail.team.tmbId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const token = await createUserSession({
|
||||||
|
userId: user._id,
|
||||||
|
teamId: userDetail.team.teamId,
|
||||||
|
tmbId: userDetail.team.tmbId,
|
||||||
|
isRoot: username === 'root',
|
||||||
|
ip: requestIp.getClientIp(req)
|
||||||
|
});
|
||||||
|
|
||||||
|
setCookie(res, token);
|
||||||
|
|
||||||
pushTrack.login({
|
pushTrack.login({
|
||||||
type: 'password',
|
type: 'password',
|
||||||
uid: user._id,
|
uid: user._id,
|
||||||
teamId: userDetail.team.teamId,
|
teamId: userDetail.team.teamId,
|
||||||
tmbId: userDetail.team.tmbId
|
tmbId: userDetail.team.tmbId
|
||||||
});
|
});
|
||||||
|
|
||||||
const token = createJWT({
|
|
||||||
...userDetail,
|
|
||||||
isRoot: username === 'root'
|
|
||||||
});
|
|
||||||
|
|
||||||
setCookie(res, token);
|
|
||||||
|
|
||||||
addOperationLog({
|
addOperationLog({
|
||||||
tmbId: userDetail.team.tmbId,
|
tmbId: userDetail.team.tmbId,
|
||||||
teamId: userDetail.team.teamId,
|
teamId: userDetail.team.teamId,
|
||||||
|
Reference in New Issue
Block a user