This commit is contained in:
2026-04-05 14:41:45 +08:00
parent a40a23ea63
commit 35cd4ade8f
7 changed files with 224 additions and 225 deletions

View File

@@ -1,8 +1,5 @@
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=
POSTGRES_DB=postgres
DATABASE_URL=postgresql://postgres:password@localhost:5432/postgres
KEVISUAL_ENV=development
REDIS_HOST=localhost
REDIS_PORT=6379

View File

@@ -49,7 +49,7 @@
"@types/busboy": "^1.5.4",
"@types/send": "^1.2.1",
"@types/ws": "^8.18.1",
"bullmq": "^5.71.1",
"bullmq": "^5.73.0",
"busboy": "^1.6.0",
"drizzle-kit": "^0.31.10",
"drizzle-orm": "^0.45.2",
@@ -59,10 +59,10 @@
"xml2js": "^0.6.2"
},
"devDependencies": {
"@ai-sdk/openai-compatible": "^2.0.37",
"@aws-sdk/client-s3": "^3.1019.0",
"@ai-sdk/openai-compatible": "^2.0.38",
"@aws-sdk/client-s3": "^3.1024.0",
"@kevisual/api": "^0.0.65",
"@kevisual/cnb": "^0.0.62",
"@kevisual/cnb": "^0.0.65",
"@kevisual/context": "^0.0.8",
"@kevisual/local-app-manager": "0.1.32",
"@kevisual/logger": "^0.0.4",
@@ -75,22 +75,22 @@
"@types/bun": "^1.3.11",
"@types/crypto-js": "^4.2.2",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^25.5.0",
"@types/node": "^25.5.2",
"@types/pg": "^8.20.0",
"@types/semver": "^7.7.1",
"@types/xml2js": "^0.4.14",
"ai": "^6.0.141",
"ai": "^6.0.146",
"archiver": "^7.0.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.20",
"dotenv": "^17.3.1",
"dotenv": "^17.4.0",
"drizzle-zod": "^0.8.3",
"es-toolkit": "^1.45.1",
"ioredis": "^5.10.1",
"jsonwebtoken": "^9.0.3",
"lunar": "^2.0.0",
"nanoid": "^5.1.7",
"p-queue": "^9.1.0",
"p-queue": "^9.1.1",
"pg": "^8.20.0",
"pm2": "^6.0.14",
"semver": "^7.7.4",

View File

@@ -1,211 +1,2 @@
import './routes/index.ts';
import './aura/index.ts';
import { app } from './app.ts';
import type { App } from '@kevisual/router';
import { User } from './models/user.ts';
import { createCookie, getSomeInfoFromReq } from './routes/user/me.ts';
import { toJSONSchema } from '@kevisual/router';
import { pick } from 'es-toolkit';
/**
* 验证上下文中的 App ID 是否与指定的 App ID 匹配
* @param {any} ctx - 上下文对象,可能包含 appId 属性
* @param {string} appId - 需要验证的目标 App ID
* @returns {boolean} 如果 ctx 中包含 appId 且匹配则返回 true否则返回 false
* @throws {Error} 如果 ctx 中包含 appId 但不匹配,则抛出 403 错误
*/
const checkAppId = (ctx: any, appId: string) => {
const _appId = ctx?.app?.appId;
if (_appId) {
if (_appId !== appId) {
ctx.throw(403, 'Invalid App ID');
}
return true;
}
return false;
}
/**
* 添加auth中间件, 用于验证token
* 添加 id: auth 必须需要user成功
* 添加 id: auth-can 可以不需要user成功有则赋值
*
* @param app
*/
export const addAuth = (app: App) => {
app
.route({
path: 'auth',
rid: 'auth',
description: '验证token必须成功, 错误返回401正确赋值到ctx.state.tokenUser',
})
.define(async (ctx) => {
const token = ctx.query.token;
// if (checkAppId(ctx, app.appId)) {
// ctx.state.tokenUser = {
// username: 'default',
// }
// return;
// }
// 已经有用户信息则直接返回,不需要重复验证
if (ctx.state.tokenUser) {
return;
}
if (!token) {
ctx.throw(401, 'Token is required');
}
const user = await User.getOauthUser(token);
if (!user) {
ctx.throw(401, 'Token is invalid');
return;
}
// console.log(`auth user: ${user.username} (${user.id})`);
const someInfo = getSomeInfoFromReq(ctx);
if (someInfo.isBrowser && !ctx.req?.cookies?.['token']) {
createCookie({ accessToken: token }, ctx);
}
ctx.state.tokenUser = user;
})
.addTo(app);
app
.route({
path: 'auth',
key: 'can',
rid: 'auth-can',
description: '验证token可以不成功错误不返回401正确赋值到ctx.state.tokenUser失败赋值null',
})
.define(async (ctx) => {
// if (checkAppId(ctx, app.appId)) {
// ctx.state.tokenUser = {
// username: 'default',
// }
// return;
// }
// 已经有用户信息则直接返回,不需要重复验证
if (ctx.state.tokenUser) {
return;
}
if (ctx.query?.token) {
const token = ctx.query.token;
const user = await User.getOauthUser(token);
if (token) {
ctx.state.tokenUser = user;
const someInfo = getSomeInfoFromReq(ctx);
if (someInfo.isBrowser && !ctx.req?.cookies?.['token']) {
createCookie({ accessToken: token }, ctx);
}
} else {
ctx.state.tokenUser = null;
}
}
})
.addTo(app);
};
addAuth(app);
app
.route({
path: 'auth',
key: 'admin',
rid: 'auth-admin',
isDebug: true,
middleware: ['auth'],
description: '验证token必须是admin用户, 错误返回403正确赋值到ctx.state.tokenAdmin',
})
.define(async (ctx) => {
// if (checkAppId(ctx, app.appId)) {
// ctx.state.tokenUser = {
// username: 'default',
// }
// return;
// }
const tokenUser = ctx.state.tokenUser;
if (!tokenUser) {
ctx.throw(401, 'No User For authorized');
}
console.log('auth-admin tokenUser', ctx.state);
if (typeof ctx.state.isAdmin !== 'undefined' && ctx.state.isAdmin === true) {
return;
}
try {
const user = await User.findOne({
id: tokenUser.id,
});
if (!user) {
ctx.throw(404, 'user not found');
}
user.setTokenUser(tokenUser);
const orgs = await user.getOrgs();
if (orgs.includes('admin')) {
ctx.body = 'admin';
} else {
ctx.throw(403, 'forbidden');
}
ctx.state.isAdmin = true;
} catch (e) {
console.error(`auth-admin error`, e);
console.error('tokenUser', tokenUser?.id, tokenUser?.username, tokenUser?.uid);
ctx.throw(500, e.message);
}
})
.addTo(app);
app
.route({
path: 'auth-check',
key: 'admin',
rid: 'check-auth-admin',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
if (!tokenUser) {
ctx.throw(401, 'No User For authorized');
}
if (typeof ctx.state.isAdmin !== 'undefined') {
return;
}
try {
const user = await User.findOne({
id: tokenUser.id,
});
if (!user) {
ctx.throw(404, 'user not found');
}
user.setTokenUser(tokenUser);
const orgs = await user.getOrgs();
if (orgs.includes('admin')) {
ctx.body = 'admin';
ctx.state.isAdmin = true;
ctx.state.tokenAdmin = {
id: user.id,
username: user.username,
orgs,
};
return;
} else {
ctx.state.isAdmin = false;
}
ctx.body = 'not admin';
} catch (e) {
console.error(`auth-admin error`, e);
console.error('tokenUser', tokenUser?.id, tokenUser?.username, tokenUser?.uid);
ctx.throw(500, e.message);
}
})
.addTo(app);
app.createRouteList({
middleware: ['auth-can']
})
app.route({
path: 'system',
key: 'version'
}).define(async (ctx) => {
ctx.body = {
version: '0.0.1',
name: 'KeVisual Backend System',
}
}).addTo(app);

197
src/routes/auth/index.ts Normal file
View File

@@ -0,0 +1,197 @@
import { app } from '@/app.ts';
import type { App } from '@kevisual/router';
import { User } from '@/models/user.ts';
import { createCookie, getSomeInfoFromReq } from '@/routes/user/me.ts';
/**
* 验证上下文中的 App ID 是否与指定的 App ID 匹配
* @param {any} ctx - 上下文对象,可能包含 appId 属性
* @param {string} appId - 需要验证的目标 App ID
* @returns {boolean} 如果 ctx 中包含 appId 且匹配则返回 true否则返回 false
* @throws {Error} 如果 ctx 中包含 appId 但不匹配,则抛出 403 错误
*/
const checkAppId = (ctx: any, appId: string) => {
const _appId = ctx?.app?.appId;
if (_appId) {
if (_appId !== appId) {
ctx.throw(403, 'Invalid App ID');
}
return true;
}
return false;
}
/**
* 添加auth中间件, 用于验证token
* 添加 id: auth 必须需要user成功
* 添加 id: auth-can 可以不需要user成功有则赋值
*
* @param app
*/
export const addAuth = (app: App) => {
app
.route({
path: 'auth',
rid: 'auth',
description: '验证token必须成功, 错误返回401正确赋值到ctx.state.tokenUser',
})
.define(async (ctx) => {
const token = ctx.query.token;
// if (checkAppId(ctx, app.appId)) {
// ctx.state.tokenUser = {
// username: 'default',
// }
// return;
// }
// 已经有用户信息则直接返回,不需要重复验证
if (ctx.state.tokenUser) {
return;
}
if (!token) {
ctx.throw(401, 'Token is required');
}
const user = await User.getOauthUser(token);
if (!user) {
ctx.throw(401, 'Token is invalid');
return;
}
// console.log(`auth user: ${user.username} (${user.id})`);
const someInfo = getSomeInfoFromReq(ctx);
if (someInfo.isBrowser && !ctx.req?.cookies?.['token']) {
createCookie({ accessToken: token }, ctx);
}
ctx.state.tokenUser = user;
})
.addTo(app);
app
.route({
path: 'auth',
key: 'can',
rid: 'auth-can',
description: '验证token可以不成功错误不返回401正确赋值到ctx.state.tokenUser失败赋值null',
})
.define(async (ctx) => {
// if (checkAppId(ctx, app.appId)) {
// ctx.state.tokenUser = {
// username: 'default',
// }
// return;
// }
// 已经有用户信息则直接返回,不需要重复验证
if (ctx.state.tokenUser) {
return;
}
if (ctx.query?.token) {
const token = ctx.query.token;
const user = await User.getOauthUser(token);
if (token) {
ctx.state.tokenUser = user;
const someInfo = getSomeInfoFromReq(ctx);
if (someInfo.isBrowser && !ctx.req?.cookies?.['token']) {
createCookie({ accessToken: token }, ctx);
}
} else {
ctx.state.tokenUser = null;
}
}
})
.addTo(app);
};
addAuth(app);
app
.route({
path: 'auth',
key: 'admin',
rid: 'auth-admin',
isDebug: true,
middleware: ['auth'],
description: '验证token必须是admin用户, 错误返回403正确赋值到ctx.state.tokenAdmin',
})
.define(async (ctx) => {
// if (checkAppId(ctx, app.appId)) {
// ctx.state.tokenUser = {
// username: 'default',
// }
// return;
// }
const tokenUser = ctx.state.tokenUser;
if (!tokenUser) {
ctx.throw(401, 'No User For authorized');
}
console.log('auth-admin tokenUser', ctx.state);
if (typeof ctx.state.isAdmin !== 'undefined' && ctx.state.isAdmin === true) {
return;
}
try {
const user = await User.findOne({
id: tokenUser.id,
});
if (!user) {
ctx.throw(404, 'user not found');
}
user.setTokenUser(tokenUser);
const orgs = await user.getOrgs();
if (orgs.includes('admin')) {
ctx.body = 'admin';
} else {
ctx.throw(403, 'forbidden');
}
ctx.state.isAdmin = true;
} catch (e) {
console.error(`auth-admin error`, e);
console.error('tokenUser', tokenUser?.id, tokenUser?.username, tokenUser?.uid);
ctx.throw(500, e.message);
}
})
.addTo(app);
app
.route({
path: 'auth-check',
key: 'admin',
rid: 'check-auth-admin',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
if (!tokenUser) {
ctx.throw(401, 'No User For authorized');
}
if (typeof ctx.state.isAdmin !== 'undefined') {
return;
}
try {
const user = await User.findOne({
id: tokenUser.id,
});
if (!user) {
ctx.throw(404, 'user not found');
}
user.setTokenUser(tokenUser);
const orgs = await user.getOrgs();
if (orgs.includes('admin')) {
ctx.body = 'admin';
ctx.state.isAdmin = true;
ctx.state.tokenAdmin = {
id: user.id,
username: user.username,
orgs,
};
return;
} else {
ctx.state.isAdmin = false;
}
ctx.body = 'not admin';
} catch (e) {
console.error(`auth-admin error`, e);
console.error('tokenUser', tokenUser?.id, tokenUser?.username, tokenUser?.uid);
ctx.throw(500, e.message);
}
})
.addTo(app);
app.createRouteList({
middleware: ['auth-can']
})

View File

@@ -24,4 +24,8 @@ import './flowme/index.ts'
import './n5-link/index.ts'
import './flowme-life/index.ts'
import './flowme-life/index.ts'
import './auth/index.ts';
import './system/index.ts';

View File

@@ -0,0 +1,11 @@
import { app } from '@/app.ts';
app.route({
path: 'system',
key: 'version'
}).define(async (ctx) => {
ctx.body = {
version: '0.0.1',
name: 'KeVisual Backend System',
}
}).addTo(app);

View File

@@ -7,7 +7,7 @@ const config = useConfig();
export const showMore = (res: any) => {
return util.inspect(res, { depth: 6, colors: true });
}
const token = 'st_r3u38c0jbhoc412ovzeeuaucygt6w5qg';
const token = '';
export const query = new Query({
url: 'http://localhost:4005/api/router',
});
@@ -15,7 +15,6 @@ const loginRes = await query.post({
path: 'user',
key: 'login',
username: 'root',
password: config.KEVISUAL_PASSWORD || '',
});
console.log('login:', showMore(loginRes));
query.beforeRequest = async (options) => {