feat: 更新开发脚本,添加新的环境变量支持,优化管理员登录流程
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
|||||||
@@ -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}/` });
|
||||||
|
|||||||
Reference in New Issue
Block a user