feat: 更新开发脚本,添加新的环境变量支持,优化管理员登录流程

This commit is contained in:
2025-12-18 03:47:07 +08:00
parent 5b610fd600
commit 6e1ffe173a
6 changed files with 91 additions and 34 deletions

View File

@@ -20,7 +20,8 @@
], ],
"scripts": { "scripts": {
"dev": "bun run src/run.ts ", "dev": "bun run src/run.ts ",
"dev:server": "ASSISTANT_CONFIG_DIR=/workspace bun --watch src/run-server.ts ", "dev:server": "bun --watch src/run-server.ts ",
"dev:cnb": "ASSISTANT_CONFIG_DIR=/workspace bun --watch src/run-server.ts ",
"dev:share": "bun --watch src/test/remote-app.ts ", "dev:share": "bun --watch src/test/remote-app.ts ",
"build:lib": "bun run bun-lib.config.mjs", "build:lib": "bun run bun-lib.config.mjs",
"postbuild:lib": "dts -i src/lib.ts -o assistant-lib.d.ts -d libs -t", "postbuild:lib": "dts -i src/lib.ts -o assistant-lib.d.ts -d libs -t",

View File

@@ -70,7 +70,7 @@ export type ReturnInitConfigType = ReturnType<typeof initConfig>;
type AuthPermission = { type AuthPermission = {
type?: 'auth-proxy' | 'public' | 'private' | 'project'; type?: 'auth-proxy' | 'public' | 'private' | 'project';
username?: string; // 用户名 username?: string; // 用户名
admin?: Omit<AuthPermission, 'admin'>; admin?: string[];
}; };
export type AssistantConfigData = { export type AssistantConfigData = {
pageApi?: string; // https://kevisual.cn pageApi?: string; // https://kevisual.cn

View File

@@ -14,7 +14,7 @@ export const getTokenUser = async (ctx: any) => {
const res = await query.post({ const res = await query.post({
path: 'user', path: 'user',
key: 'me', key: 'me',
token: ctx.state.token, token: ctx.state.token || ctx.query.token,
}); });
if (res.code !== 200) { if (res.code !== 200) {
return ctx.throw(401, 'not login'); return ctx.throw(401, 'not login');
@@ -26,7 +26,7 @@ const checkAuth = async (ctx: any, isAdmin = false) => {
const config = assistantConfig.getConfig(); const config = assistantConfig.getConfig();
const { auth = {} } = config; const { auth = {} } = config;
const token = ctx.query.token; const token = ctx.query.token;
console.log('checkAuth', ctx.query, { token });
if (!token) { if (!token) {
return ctx.throw(401, 'not login'); return ctx.throw(401, 'not login');
} }
@@ -47,8 +47,17 @@ const checkAuth = async (ctx: any, isAdmin = false) => {
auth.username = username; auth.username = username;
assistantConfig.setConfig({ auth }); assistantConfig.setConfig({ auth });
} }
if (isAdmin) { if (isAdmin && auth.username) {
if (auth.username && auth.username !== username) { const admins = config.auth?.admin || [];
let isCheckAdmin = false;
const admin = auth.username;
if (admin === username) {
isCheckAdmin = true;
}
if (!isCheckAdmin && admins.length > 0 && admins.includes(username)) {
isCheckAdmin = true;
}
if (!isCheckAdmin) {
return ctx.throw(403, 'not admin user'); return ctx.throw(403, 'not admin user');
} }
} }
@@ -70,6 +79,7 @@ app
description: '管理员鉴权, 获取用户信息,并验证是否为管理员。', description: '管理员鉴权, 获取用户信息,并验证是否为管理员。',
}) })
.define(async (ctx) => { .define(async (ctx) => {
console.log('query', ctx.query);
await checkAuth(ctx, true); await checkAuth(ctx, true);
}) })
.addTo(app); .addTo(app);

View File

@@ -6,29 +6,64 @@ app.route({
description: '管理员用户登录', description: '管理员用户登录',
}).define(async (ctx) => { }).define(async (ctx) => {
const { username, password } = ctx.query; const { username, password } = ctx.query;
const query = assistantConfig.query;
const auth = assistantConfig.getConfig().auth || {}; const auth = assistantConfig.getConfig().auth || {};
const res = await query.post({ if (auth && auth.username && auth.username !== username) {
path: 'user',
key: 'login',
data: {
username,
password,
},
})
if (res.code !== 200) {
return ctx.throw(401, 'login failed');
}
const loginUser = res.data.username;
if (auth.username && loginUser !== auth.username) {
return ctx.throw(403, 'login user is not admin user'); return ctx.throw(403, 'login user is not admin user');
} }
if (!auth.username) { // 发起请求,转发客户端 cookie
// 初始管理员账号 const res = await fetch(`${assistantConfig.baseURL}`, {
auth.username = loginUser; method: 'POST',
assistantConfig.setConfig({ auth }); headers: {
} 'Content-Type': 'application/json',
// 保存配置 },
body: JSON.stringify({
path: 'user',
key: 'login',
username,
password,
}),
});
ctx.body = res.data; // 转发上游服务器返回的所有 set-cookie支持多个 cookie
const setCookieHeaders = res.headers.getSetCookie?.() || [];
if (setCookieHeaders.length > 0) {
// 设置多个 cookie 到原生 http.ServerResponse
ctx.res.setHeader('Set-Cookie', setCookieHeaders);
} else {
// 兼容旧版本,使用 get 方法
const setCookieHeader = res.headers.get('set-cookie');
if (setCookieHeader) {
ctx.res.setHeader('Set-Cookie', setCookieHeader);
}
}
const responseData = await res.json();
console.debug('admin login response', { res: responseData });
if (responseData.code !== 200) {
console.debug('admin login failed', { res: responseData });
return ctx.throw(401, 'login failed');
}
const me = await assistantConfig.query.post({
path: 'user',
key: 'me',
token: responseData.data.token,
})
if (me.code === 200) {
const loginUser = me.data.username;
if (auth.username && loginUser !== auth.username) {
return ctx.throw(403, 'login user is not admin user');
}
if (!auth.username) {
// 初始管理员账号
auth.username = loginUser;
if (!auth.type) {
auth.type = 'public';
}
assistantConfig.setConfig({ auth });
console.log('set first admin user', { username: loginUser });
}
// 保存配置
}
ctx.body = responseData.data;
}).addTo(app); }).addTo(app);

View File

@@ -50,6 +50,9 @@ export class AssistantInit extends AssistantConfig {
} }
return this.#query; return this.#query;
} }
get baseURL() {
return `${this.getConfig()?.pageApi || 'https://kevisual.cn'}/api/router`;
}
setQuery(query?: Query) { setQuery(query?: Query) {
this.#query = query || new Query({ this.#query = query || new Query({
url: `${this.getConfig()?.pageApi || 'https://kevisual.cn'}/api/router`, url: `${this.getConfig()?.pageApi || 'https://kevisual.cn'}/api/router`,

View File

@@ -8,15 +8,21 @@ localProxy.initFromAssistantConfig(assistantConfig);
export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResponse) => { export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResponse) => {
const _assistantConfig = assistantConfig.getCacheAssistantConfig(); const _assistantConfig = assistantConfig.getCacheAssistantConfig();
const auth = _assistantConfig?.auth; const home = _assistantConfig?.home || '/root/home';
if (!auth.username) { const auth = _assistantConfig?.auth || {};
let noAdmin = !auth.username;
const toSetting = () => {
res.writeHead(302, { Location: `/root/cli/setting/` }); res.writeHead(302, { Location: `/root/cli/setting/` });
return res.end(); res.end();
return true;
} }
const url = new URL(req.url, 'http://localhost'); const url = new URL(req.url, 'http://localhost');
const pathname = decodeURIComponent(url.pathname); const pathname = decodeURIComponent(url.pathname);
if (pathname === '/' && _assistantConfig?.home) { if (pathname === '/') {
res.writeHead(302, { Location: `${_assistantConfig?.home}/` }); if (noAdmin) {
return toSetting();
}
res.writeHead(302, { Location: `${home}/` });
return res.end(); return res.end();
} }
if (pathname.startsWith('/favicon.ico')) { if (pathname.startsWith('/favicon.ico')) {
@@ -25,8 +31,7 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
return; return;
} }
if (pathname.startsWith('/client')) { if (pathname.startsWith('/client')) {
logger.info('url', { url: req.url }); logger.debug('handle by router', { url: req.url });
console.debug('handle by router');
return; return;
} }
// client, api, v1, serve 开头的拦截 // client, api, v1, serve 开头的拦截
@@ -47,6 +52,9 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
res.end('Not Found Proxy'); res.end('Not Found Proxy');
return; return;
} }
if (noAdmin) {
return toSetting();
}
if (_app && urls.length === 3) { if (_app && urls.length === 3) {
// 重定向到 // 重定向到
res.writeHead(302, { Location: `${req.url}/` }); res.writeHead(302, { Location: `${req.url}/` });