diff --git a/.env.example b/.env.example index 7c0ee9d..e5e5c6b 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/package.json b/package.json index 5cd288d..92c8fab 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/route.ts b/src/route.ts index b2e19c9..36d0366 100644 --- a/src/route.ts +++ b/src/route.ts @@ -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); \ No newline at end of file diff --git a/src/routes/auth/index.ts b/src/routes/auth/index.ts new file mode 100644 index 0000000..6db94a0 --- /dev/null +++ b/src/routes/auth/index.ts @@ -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'] +}) \ No newline at end of file diff --git a/src/routes/index.ts b/src/routes/index.ts index 1f6b8e4..7165a0d 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -24,4 +24,8 @@ import './flowme/index.ts' import './n5-link/index.ts' -import './flowme-life/index.ts' \ No newline at end of file +import './flowme-life/index.ts' + +import './auth/index.ts'; + +import './system/index.ts'; \ No newline at end of file diff --git a/src/routes/system/index.ts b/src/routes/system/index.ts new file mode 100644 index 0000000..4e898b2 --- /dev/null +++ b/src/routes/system/index.ts @@ -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); \ No newline at end of file diff --git a/src/test/common-query.ts b/src/test/common-query.ts index 6d7ce08..0ef069e 100644 --- a/src/test/common-query.ts +++ b/src/test/common-query.ts @@ -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) => {