This commit is contained in:
Archer
2023-11-09 09:46:57 +08:00
committed by GitHub
parent 661ee79943
commit 8bb5588305
402 changed files with 9899 additions and 5967 deletions

View File

@@ -1,206 +0,0 @@
import type { NextApiResponse, NextApiRequest } from 'next';
import Cookie from 'cookie';
import jwt from 'jsonwebtoken';
import { authOpenApiKey } from '../openapi/auth';
import { authOutLinkId } from '../outLink/auth';
import { MongoUser } from './schema';
import type { UserModelSchema } from '@fastgpt/global/support/user/type';
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
export enum AuthUserTypeEnum {
token = 'token',
root = 'root',
apikey = 'apikey',
outLink = 'outLink'
}
/* auth balance */
export const authBalanceByUid = async (uid: string) => {
const user = await MongoUser.findById<UserModelSchema>(
uid,
'_id username balance openaiAccount timezone'
);
if (!user) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
if (user.balance <= 0) {
return Promise.reject(ERROR_ENUM.insufficientQuota);
}
return user;
};
/* uniform auth user */
export const authUser = async ({
req,
authToken = false,
authRoot = false,
authApiKey = false,
authBalance = false,
authOutLink
}: {
req: NextApiRequest;
authToken?: boolean;
authRoot?: boolean;
authApiKey?: boolean;
authBalance?: boolean;
authOutLink?: boolean;
}) => {
const authCookieToken = async (cookie?: string, token?: string): Promise<string> => {
// 获取 cookie
const cookies = Cookie.parse(cookie || '');
const cookieToken = cookies.token || token;
if (!cookieToken) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return await authJWT(cookieToken);
};
// from authorization get apikey
const parseAuthorization = async (authorization?: string) => {
if (!authorization) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
// Bearer fastgpt-xxxx-appId
const auth = authorization.split(' ')[1];
if (!auth) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
const { apikey, appId: authorizationAppid = '' } = await (async () => {
const arr = auth.split('-');
// abandon
if (arr.length === 3) {
return {
apikey: `${arr[0]}-${arr[1]}`,
appId: arr[2]
};
}
if (arr.length === 2) {
return {
apikey: auth
};
}
return Promise.reject(ERROR_ENUM.unAuthorization);
})();
// auth apikey
const { userId, appId: apiKeyAppId = '' } = await authOpenApiKey({ apikey });
return {
uid: userId,
apikey,
appId: apiKeyAppId || authorizationAppid
};
};
// root user
const parseRootKey = async (rootKey?: string, userId = '') => {
if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return userId;
};
const { cookie, token, apikey, rootkey, userid, authorization } = (req.headers || {}) as {
cookie?: string;
token?: string;
apikey?: string;
rootkey?: string; // abandon
userid?: string;
authorization?: string;
};
const { shareId } = (req?.body || {}) as { shareId?: string };
let uid = '';
let appId = '';
let openApiKey = apikey;
let authType: `${AuthUserTypeEnum}` = AuthUserTypeEnum.token;
if (authOutLink && shareId) {
const res = await authOutLinkId({ id: shareId });
uid = res.userId;
authType = AuthUserTypeEnum.outLink;
} else if (authToken && (cookie || token)) {
// user token(from fastgpt web)
uid = await authCookieToken(cookie, token);
authType = AuthUserTypeEnum.token;
} else if (authRoot && rootkey) {
// root user
uid = await parseRootKey(rootkey, userid);
authType = AuthUserTypeEnum.root;
} else if (authApiKey && apikey) {
// apikey
const parseResult = await authOpenApiKey({ apikey });
uid = parseResult.userId;
authType = AuthUserTypeEnum.apikey;
openApiKey = parseResult.apikey;
} else if (authApiKey && authorization) {
// apikey from authorization
const authResponse = await parseAuthorization(authorization);
uid = authResponse.uid;
appId = authResponse.appId;
openApiKey = authResponse.apikey;
authType = AuthUserTypeEnum.apikey;
}
// not rootUser and no uid, reject request
if (!rootkey && !uid) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
// balance check
const user = await (() => {
if (authBalance) {
return authBalanceByUid(uid);
}
})();
return {
userId: String(uid),
appId,
authType,
user,
apikey: openApiKey
};
};
/* 生成 token */
export function generateToken(userId: string) {
const key = process.env.TOKEN_KEY as string;
const token = jwt.sign(
{
userId,
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7
},
key
);
return token;
}
// auth token
export function authJWT(token: string) {
return new Promise<string>((resolve, reject) => {
const key = process.env.TOKEN_KEY as string;
jwt.verify(token, key, function (err, decoded: any) {
if (err || !decoded?.userId) {
reject(ERROR_ENUM.unAuthorization);
return;
}
resolve(decoded.userId);
});
});
}
/* set cookie */
export const setCookie = (res: NextApiResponse, token: string) => {
res.setHeader(
'Set-Cookie',
`token=${token}; Path=/; HttpOnly; Max-Age=604800; Samesite=None; Secure;`
);
};
/* clear cookie */
export const clearCookie = (res: NextApiResponse) => {
res.setHeader('Set-Cookie', 'token=; Path=/; Max-Age=0');
};

View File

@@ -0,0 +1,11 @@
import { MongoUser } from './schema';
export async function authUserExist({ userId, username }: { userId?: string; username?: string }) {
if (userId) {
return MongoUser.findOne({ _id: userId });
}
if (username) {
return MongoUser.findOne({ username });
}
return null;
}

View File

@@ -1,45 +0,0 @@
import { MongoUserInform } from './schema';
import { MongoUser } from '../schema';
import { InformTypeEnum } from '@fastgpt/global/support/user/constant';
export type SendInformProps = {
type: `${InformTypeEnum}`;
title: string;
content: string;
};
export async function sendInform2AllUser({ type, title, content }: SendInformProps) {
const users = await MongoUser.find({}, '_id');
await MongoUserInform.insertMany(
users.map(({ _id }) => ({
type,
title,
content,
userId: _id
}))
);
}
export async function sendInform2OneUser({
type,
title,
content,
userId
}: SendInformProps & { userId: string }) {
const inform = await MongoUserInform.findOne({
type,
title,
content,
userId,
time: { $gte: new Date(Date.now() - 5 * 60 * 1000) }
});
if (inform) return;
await MongoUserInform.create({
type,
title,
content,
userId
});
}

View File

@@ -1,42 +0,0 @@
import { connectionMongo, type Model } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo;
import type { UserInformSchema } from '@fastgpt/global/support/user/type.d';
import { InformTypeMap } from '@fastgpt/global/support/user/constant';
const InformSchema = new Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
time: {
type: Date,
default: () => new Date()
},
type: {
type: String,
enum: Object.keys(InformTypeMap)
},
title: {
type: String,
required: true
},
content: {
type: String,
required: true
},
read: {
type: Boolean,
default: false
}
});
try {
InformSchema.index({ time: -1 });
InformSchema.index({ userId: 1 });
} catch (error) {
console.log(error);
}
export const MongoUserInform: Model<UserInformSchema> =
models['inform'] || model('inform', InformSchema);

View File

@@ -1,7 +1,7 @@
import { connectionMongo, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { hashStr } from '@fastgpt/global/common/string/tools';
import { PRICE_SCALE } from '@fastgpt/global/common/bill/constants';
import { PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants';
import type { UserModelSchema } from '@fastgpt/global/support/user/type';
export const userCollectionName = 'users';

View File

@@ -0,0 +1,125 @@
import { TeamItemType } from '@fastgpt/global/support/user/team/type';
import { connectionMongo, Types } from '../../../common/mongo';
import {
TeamMemberRoleEnum,
TeamMemberStatusEnum,
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
export async function getTeamInfoByTmbId({
tmbId,
userId
}: {
tmbId?: string;
userId?: string;
}): Promise<TeamItemType> {
if (!tmbId && !userId) {
return Promise.reject('tmbId or userId is required');
}
const db = connectionMongo?.connection?.db;
const TeamMember = db.collection(TeamMemberCollectionName);
const results = await TeamMember.aggregate([
{
$match: tmbId
? {
_id: new Types.ObjectId(tmbId)
}
: {
userId: new Types.ObjectId(userId),
defaultTeam: true
}
},
{
$lookup: {
from: TeamCollectionName, // 关联的集合名
localField: 'teamId', // TeamMember 集合中用于关联的字段
foreignField: '_id', // Team 集合中用于关联的字段
as: 'team' // 查询结果中的字段名,存放关联查询的结果
}
},
{
$unwind: '$team' // 将查询结果中的 team 字段展开,变成一个对象
}
]).toArray();
const tmb = results[0];
if (!tmb) {
return Promise.reject('team not exist');
}
return {
userId: String(tmb.userId),
teamId: String(tmb.teamId),
teamName: tmb.team.name,
avatar: tmb.team.avatar,
balance: tmb.team.balance,
tmbId: String(tmb._id),
role: tmb.role,
status: tmb.status,
defaultTeam: tmb.defaultTeam,
canWrite: tmb.role !== TeamMemberRoleEnum.visitor,
maxSize: tmb.team.maxSize
};
}
export async function createDefaultTeam({
userId,
teamName = 'My Team',
avatar = '/icon/logo.svg',
balance = 0,
maxSize = 5
}: {
userId: string;
teamName?: string;
avatar?: string;
balance?: number;
maxSize?: number;
}) {
const db = connectionMongo.connection.db;
const Team = db.collection(TeamCollectionName);
const TeamMember = db.collection(TeamMemberCollectionName);
// auth default team
const tmb = await TeamMember.findOne({
userId: new Types.ObjectId(userId),
defaultTeam: true
});
if (!tmb) {
console.log('create default team', userId);
// create
const { insertedId } = await Team.insertOne({
ownerId: userId,
name: teamName,
avatar,
balance,
maxSize,
createTime: new Date()
});
await TeamMember.insertOne({
teamId: insertedId,
userId,
role: TeamMemberRoleEnum.owner,
status: TeamMemberStatusEnum.active,
createTime: new Date(),
defaultTeam: true
});
} else {
console.log('default team exist', userId);
await Team.updateOne(
{
_id: new Types.ObjectId(tmb.teamId)
},
{
$set: {
balance,
maxSize
}
}
);
}
}