@aalis/plugin-webui-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/auth.d.ts +43 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +279 -0
- package/dist/auth.js.map +1 -0
- package/dist/gate.d.ts +39 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +43 -0
- package/dist/gate.js.map +1 -0
- package/dist/i18n.d.ts +3 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +37 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1341 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +113 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +59 -0
- package/dist/protocol.js.map +1 -0
- package/dist/routes/files.d.ts +13 -0
- package/dist/routes/files.d.ts.map +1 -0
- package/dist/routes/files.js +127 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/marketplace.d.ts +56 -0
- package/dist/routes/marketplace.d.ts.map +1 -0
- package/dist/routes/marketplace.js +101 -0
- package/dist/routes/marketplace.js.map +1 -0
- package/dist/routes/plugins.d.ts +11 -0
- package/dist/routes/plugins.d.ts.map +1 -0
- package/dist/routes/plugins.js +388 -0
- package/dist/routes/plugins.js.map +1 -0
- package/dist/routes/proxy.d.ts +12 -0
- package/dist/routes/proxy.d.ts.map +1 -0
- package/dist/routes/proxy.js +114 -0
- package/dist/routes/proxy.js.map +1 -0
- package/dist/routes/services.d.ts +14 -0
- package/dist/routes/services.d.ts.map +1 -0
- package/dist/routes/services.js +237 -0
- package/dist/routes/services.js.map +1 -0
- package/dist/routes/system.d.ts +7 -0
- package/dist/routes/system.d.ts.map +1 -0
- package/dist/routes/system.js +95 -0
- package/dist/routes/system.js.map +1 -0
- package/dist/routes/uploaded-files.d.ts +10 -0
- package/dist/routes/uploaded-files.d.ts.map +1 -0
- package/dist/routes/uploaded-files.js +162 -0
- package/dist/routes/uploaded-files.js.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @aalis/plugin-webui-server
|
|
2
|
+
|
|
3
|
+
运行时插件
|
|
4
|
+
|
|
5
|
+
## 角色
|
|
6
|
+
|
|
7
|
+
运行时插件
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @aalis/plugin-webui-server
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 文档
|
|
16
|
+
|
|
17
|
+
详见 [docs/plugins/plugin-webui-server.md](../../docs/plugins/plugin-webui-server.md)。
|
|
18
|
+
|
|
19
|
+
## 许可
|
|
20
|
+
|
|
21
|
+
见仓库根目录 LICENSE。
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { Logger } from '@aalis/core';
|
|
3
|
+
import type { UserIdentity } from '@aalis/plugin-authority-api';
|
|
4
|
+
import type { ProcessService } from '@aalis/plugin-process-api';
|
|
5
|
+
import type { RequestHandler } from 'express';
|
|
6
|
+
/**
|
|
7
|
+
* 账户校验器 —— auth 系统对 authority 服务的最小依赖面。
|
|
8
|
+
*
|
|
9
|
+
* 以函数注入而非服务引用,调用方应惰性解析(authority 可能晚于 webui-server
|
|
10
|
+
* 激活或被热替换)。
|
|
11
|
+
*/
|
|
12
|
+
export interface AccountVerifier {
|
|
13
|
+
/** 校验账户密码(无账户/不匹配均 false) */
|
|
14
|
+
verify(username: string, password: string): boolean | Promise<boolean>;
|
|
15
|
+
/** 是否存在任何可登录账户(决定登录页是否显示账户表单) */
|
|
16
|
+
hasAccounts(): boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface AuthSystem {
|
|
19
|
+
/** Express 中间件:处理 ?token= 重定向、登录端点、未认证拦截、登录页 */
|
|
20
|
+
middleware: RequestHandler;
|
|
21
|
+
/** 校验 WebSocket 升级请求是否已认证(cookie 读取) */
|
|
22
|
+
verifyWsClient(req: IncomingMessage): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* 解析请求的调用者身份:账户 session → `webui:<username>`;
|
|
25
|
+
* 单 token 模式 → `webui:console`(owner 级,单人模式语义);未认证 → undefined。
|
|
26
|
+
*/
|
|
27
|
+
identify(req: Pick<IncomingMessage, 'headers'>): UserIdentity | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 创建认证系统:账户 session + 单 token 双模式。
|
|
31
|
+
*
|
|
32
|
+
* - 账户登录(username/password)→ 服务端随机 session(内存表,重启失效),
|
|
33
|
+
* cookie `aalis_webui_session`,身份为 `webui:<username>`
|
|
34
|
+
* - token 登录(向后兼容的单人模式)→ cookie `aalis_webui_token`,身份 `webui:console`
|
|
35
|
+
* - 两者并存时 session 身份优先
|
|
36
|
+
*/
|
|
37
|
+
export declare function createAuthSystem(token: string, logger: Logger, accounts?: AccountVerifier, tokenLogin?: () => boolean): AuthSystem;
|
|
38
|
+
/**
|
|
39
|
+
* 跨平台打开默认浏览器到指定 URL(无外部依赖)
|
|
40
|
+
* 失败静默;此为便利功能,不影响服务启动。
|
|
41
|
+
*/
|
|
42
|
+
export declare function openBrowser(url: string, proc: ProcessService): void;
|
|
43
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAuG9C;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,iCAAiC;IACjC,WAAW,IAAI,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,gDAAgD;IAChD,UAAU,EAAE,cAAc,CAAC;IAC3B,wCAAwC;IACxC,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC;IAC9C;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,YAAY,GAAG,SAAS,CAAC;CAC3E;AAWD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,eAAe,EAC1B,UAAU,GAAE,MAAM,OAAoB,GACrC,UAAU,CAwJZ;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAenE"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
const TOKEN_COOKIE = 'aalis_webui_token';
|
|
2
|
+
const SESSION_COOKIE = 'aalis_webui_session';
|
|
3
|
+
function parseCookieValue(header, name) {
|
|
4
|
+
if (!header)
|
|
5
|
+
return undefined;
|
|
6
|
+
for (const pair of header.split(';')) {
|
|
7
|
+
const eq = pair.indexOf('=');
|
|
8
|
+
if (eq < 0)
|
|
9
|
+
continue;
|
|
10
|
+
const k = pair.slice(0, eq).trim();
|
|
11
|
+
if (k === name) {
|
|
12
|
+
try {
|
|
13
|
+
return decodeURIComponent(pair.slice(eq + 1).trim());
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function buildSetCookie(name, value, maxAgeSec) {
|
|
23
|
+
return `${name}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${maxAgeSec}`;
|
|
24
|
+
}
|
|
25
|
+
function buildClearCookie(name) {
|
|
26
|
+
return `${name}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 登录页:自包含 HTML,不引入外部资源。
|
|
30
|
+
*
|
|
31
|
+
* 存在账户时默认显示「账户登录」表单(用户名+密码),token 登录折叠为次选
|
|
32
|
+
* (tokenMode=disabled 时整体隐藏);无账户时只显示 token 表单(兜底,防锁死)。
|
|
33
|
+
* 两种提交都打 POST /api/auth/login。
|
|
34
|
+
*/
|
|
35
|
+
function loginPageHtml(hasAccounts, showToken, reason) {
|
|
36
|
+
const note = reason ? `<p class="note">${escapeHtml(reason)}</p>` : '';
|
|
37
|
+
const tokenDetails = showToken
|
|
38
|
+
? `<details style="margin-top:14px"><summary>使用访问 token 登录</summary>
|
|
39
|
+
<input id="t" type="password" autocomplete="off" placeholder="访问 token" style="margin-top:8px">
|
|
40
|
+
<button type="button" id="tb" style="background:#2a3038">Token 登录</button>
|
|
41
|
+
</details>`
|
|
42
|
+
: '';
|
|
43
|
+
const accountForm = hasAccounts
|
|
44
|
+
? `<input id="u" type="text" autocomplete="username" placeholder="用户名" autofocus required>
|
|
45
|
+
<input id="p" type="password" autocomplete="current-password" placeholder="密码" required style="margin-top:8px">
|
|
46
|
+
<button type="submit" id="b">登录</button>
|
|
47
|
+
${tokenDetails}`
|
|
48
|
+
: `<p>请输入启动日志或 data/webui/access.txt 中显示的访问 token。</p>
|
|
49
|
+
<input id="t" type="password" autocomplete="off" placeholder="访问 token" autofocus required>
|
|
50
|
+
<button type="submit" id="b">登录</button>`;
|
|
51
|
+
return `<!DOCTYPE html><html lang="zh-CN"><head>
|
|
52
|
+
<meta charset="utf-8"><title>Aalis WebUI 登录</title>
|
|
53
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
54
|
+
<style>
|
|
55
|
+
:root{color-scheme:light dark}
|
|
56
|
+
*{box-sizing:border-box}
|
|
57
|
+
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#0b0d10;color:#e6e8eb}
|
|
58
|
+
.card{width:min(420px,90vw);padding:32px;background:#16191d;border:1px solid #262b32;border-radius:12px;box-shadow:0 12px 36px rgba(0,0,0,.4)}
|
|
59
|
+
h1{margin:0 0 8px;font-size:22px;font-weight:600}
|
|
60
|
+
p{margin:0 0 16px;color:#8b95a1;font-size:14px;line-height:1.5}
|
|
61
|
+
p.note{color:#ffb300}
|
|
62
|
+
summary{color:#8b95a1;font-size:13px;cursor:pointer}
|
|
63
|
+
input{width:100%;padding:10px 12px;font-size:14px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:#0b0d10;color:#e6e8eb;border:1px solid #2a3038;border-radius:6px;outline:none}
|
|
64
|
+
input:focus{border-color:#5b8def}
|
|
65
|
+
button{width:100%;margin-top:12px;padding:10px;font-size:14px;font-weight:500;background:#5b8def;color:#fff;border:0;border-radius:6px;cursor:pointer}
|
|
66
|
+
button:hover{filter:brightness(1.08)}
|
|
67
|
+
button:disabled{opacity:.5;cursor:wait}
|
|
68
|
+
.err{margin-top:10px;color:#ff6b6b;font-size:13px;min-height:18px}
|
|
69
|
+
</style></head>
|
|
70
|
+
<body><form class="card" id="f">
|
|
71
|
+
<h1>Aalis WebUI</h1>
|
|
72
|
+
${note}
|
|
73
|
+
${accountForm}
|
|
74
|
+
<div class="err" id="e"></div>
|
|
75
|
+
</form>
|
|
76
|
+
<script>
|
|
77
|
+
const $=id=>document.getElementById(id);
|
|
78
|
+
const f=$('f'),e=$('e');
|
|
79
|
+
async function login(body,btn){
|
|
80
|
+
e.textContent='';btn.disabled=true;
|
|
81
|
+
try{
|
|
82
|
+
const r=await fetch('/api/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
83
|
+
if(r.ok){location.replace('/');}else{const j=await r.json().catch(()=>({}));e.textContent=j.error||'登录失败';btn.disabled=false;}
|
|
84
|
+
}catch(err){e.textContent='网络错误';btn.disabled=false;}
|
|
85
|
+
}
|
|
86
|
+
f.addEventListener('submit',ev=>{
|
|
87
|
+
ev.preventDefault();
|
|
88
|
+
const u=$('u'),t=$('t');
|
|
89
|
+
if(u&&u.value.trim()){login({username:u.value.trim(),password:$('p').value},$('b'));}
|
|
90
|
+
else if(t&&t.value.trim()){login({token:t.value.trim()},$('b'));}
|
|
91
|
+
});
|
|
92
|
+
const tb=$('tb');
|
|
93
|
+
if(tb)tb.addEventListener('click',()=>{const t=$('t');if(t.value.trim())login({token:t.value.trim()},tb);});
|
|
94
|
+
</script></body></html>`;
|
|
95
|
+
}
|
|
96
|
+
function escapeHtml(s) {
|
|
97
|
+
return s.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
|
|
98
|
+
}
|
|
99
|
+
const MAX_FAILURES = 5;
|
|
100
|
+
const LOCK_MS = 60_000;
|
|
101
|
+
/**
|
|
102
|
+
* 创建认证系统:账户 session + 单 token 双模式。
|
|
103
|
+
*
|
|
104
|
+
* - 账户登录(username/password)→ 服务端随机 session(内存表,重启失效),
|
|
105
|
+
* cookie `aalis_webui_session`,身份为 `webui:<username>`
|
|
106
|
+
* - token 登录(向后兼容的单人模式)→ cookie `aalis_webui_token`,身份 `webui:console`
|
|
107
|
+
* - 两者并存时 session 身份优先
|
|
108
|
+
*/
|
|
109
|
+
export function createAuthSystem(token, logger, accounts, tokenLogin = () => true) {
|
|
110
|
+
const cookieMaxAge = 30 * 24 * 3600; // 30d;进程重启 token 轮换/session 表清空后无效
|
|
111
|
+
const sessions = new Map();
|
|
112
|
+
const failures = new Map();
|
|
113
|
+
/** token 登录是否可用:宿主开关(tokenMode!=disabled)|| 无账户兜底(防锁死) */
|
|
114
|
+
function tokenAllowed() {
|
|
115
|
+
return tokenLogin() || !(accounts?.hasAccounts() ?? false);
|
|
116
|
+
}
|
|
117
|
+
function newSession(username) {
|
|
118
|
+
// 顺带清扫过期 session(登录是低频操作,无需独立 timer——审计 MEDIUM #10)
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
for (const [id, s] of sessions)
|
|
121
|
+
if (s.expiresAt <= now)
|
|
122
|
+
sessions.delete(id);
|
|
123
|
+
const id = Buffer.from(crypto.getRandomValues(new Uint8Array(24))).toString('hex');
|
|
124
|
+
sessions.set(id, { username, expiresAt: now + cookieMaxAge * 1000 });
|
|
125
|
+
return id;
|
|
126
|
+
}
|
|
127
|
+
function sessionUser(req) {
|
|
128
|
+
const id = parseCookieValue(req.headers.cookie, SESSION_COOKIE);
|
|
129
|
+
if (!id)
|
|
130
|
+
return undefined;
|
|
131
|
+
const s = sessions.get(id);
|
|
132
|
+
if (!s)
|
|
133
|
+
return undefined;
|
|
134
|
+
if (s.expiresAt <= Date.now()) {
|
|
135
|
+
sessions.delete(id);
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
return s.username;
|
|
139
|
+
}
|
|
140
|
+
function tokenAuthed(req) {
|
|
141
|
+
if (!tokenAllowed())
|
|
142
|
+
return false;
|
|
143
|
+
const cookieToken = parseCookieValue(req.headers.cookie, TOKEN_COOKIE);
|
|
144
|
+
return !!cookieToken && cookieToken === token;
|
|
145
|
+
}
|
|
146
|
+
function identify(req) {
|
|
147
|
+
const username = sessionUser(req);
|
|
148
|
+
if (username)
|
|
149
|
+
return { platform: 'webui', userId: username };
|
|
150
|
+
if (tokenAuthed(req))
|
|
151
|
+
return { platform: 'webui', userId: 'console' };
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
function checkLocked(username) {
|
|
155
|
+
const rec = failures.get(username);
|
|
156
|
+
return !!rec && rec.count >= MAX_FAILURES && rec.lockedUntil > Date.now();
|
|
157
|
+
}
|
|
158
|
+
function recordFailure(username) {
|
|
159
|
+
const rec = failures.get(username);
|
|
160
|
+
if (rec && rec.lockedUntil > Date.now()) {
|
|
161
|
+
rec.count += 1;
|
|
162
|
+
rec.lockedUntil = Date.now() + LOCK_MS;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
failures.set(username, { count: 1, lockedUntil: Date.now() + LOCK_MS });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const middleware = async (req, res, next) => {
|
|
169
|
+
const url = req.path;
|
|
170
|
+
const qToken = typeof req.query?.token === 'string' ? req.query.token : undefined;
|
|
171
|
+
const hasAccounts = accounts?.hasAccounts() ?? false;
|
|
172
|
+
// 1. ?token= 命中 → 设置 cookie → 302 到不带 query 的同路径
|
|
173
|
+
if (req.method === 'GET' && qToken && tokenAllowed()) {
|
|
174
|
+
if (qToken === token) {
|
|
175
|
+
res.setHeader('Set-Cookie', buildSetCookie(TOKEN_COOKIE, token, cookieMaxAge));
|
|
176
|
+
res.redirect(302, url);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// ?token 错误:落到登录页(不向 next 透)
|
|
180
|
+
res
|
|
181
|
+
.status(401)
|
|
182
|
+
.type('html')
|
|
183
|
+
.send(loginPageHtml(hasAccounts, true, 'URL 中的 token 不正确,请手动输入。'));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// 2. 登录端点:{username, password}(账户)或 {token}(单人模式)
|
|
187
|
+
if (url === '/api/auth/login' && req.method === 'POST') {
|
|
188
|
+
const body = req.body ?? {};
|
|
189
|
+
if (typeof body.username === 'string' && typeof body.password === 'string') {
|
|
190
|
+
const username = body.username.trim();
|
|
191
|
+
if (checkLocked(username)) {
|
|
192
|
+
res.status(429).json({ ok: false, error: '尝试次数过多,请稍后再试' });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (username && (await accounts?.verify(username, body.password))) {
|
|
196
|
+
failures.delete(username);
|
|
197
|
+
res.setHeader('Set-Cookie', buildSetCookie(SESSION_COOKIE, newSession(username), cookieMaxAge));
|
|
198
|
+
logger.info(`账户登录: webui:${username}`);
|
|
199
|
+
res.json({ ok: true, identity: { platform: 'webui', userId: username } });
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
recordFailure(username);
|
|
203
|
+
res.status(401).json({ ok: false, error: '用户名或密码不正确' });
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (typeof body.token === 'string' && !tokenAllowed()) {
|
|
208
|
+
res.status(401).json({ ok: false, error: 'token 登录已禁用,请使用账户密码' });
|
|
209
|
+
}
|
|
210
|
+
else if (typeof body.token === 'string' && body.token === token) {
|
|
211
|
+
res.setHeader('Set-Cookie', buildSetCookie(TOKEN_COOKIE, token, cookieMaxAge));
|
|
212
|
+
res.json({ ok: true, identity: { platform: 'webui', userId: 'console' } });
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
res.status(401).json({ ok: false, error: 'token 不正确' });
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// 3. 状态查询(含当前身份,供前端顶栏展示)
|
|
220
|
+
if (url === '/api/auth/status' && req.method === 'GET') {
|
|
221
|
+
const identity = identify(req);
|
|
222
|
+
res.json({ authed: !!identity, identity });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// 4. 注销:清 session 记录与两种 cookie
|
|
226
|
+
if (url === '/api/auth/logout' && req.method === 'POST') {
|
|
227
|
+
const sid = parseCookieValue(req.headers.cookie, SESSION_COOKIE);
|
|
228
|
+
if (sid)
|
|
229
|
+
sessions.delete(sid);
|
|
230
|
+
res.setHeader('Set-Cookie', [buildClearCookie(SESSION_COOKIE), buildClearCookie(TOKEN_COOKIE)]);
|
|
231
|
+
res.json({ ok: true });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// 5. 已认证 → 放行
|
|
235
|
+
if (identify(req)) {
|
|
236
|
+
next();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// 6. 未认证:API 一律 401
|
|
240
|
+
if (url.startsWith('/api/')) {
|
|
241
|
+
res.status(401).json({ error: 'unauthenticated' });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// 7. 未认证 GET:返回内联登录页(覆盖 SPA shell,不暴露任何静态资源)
|
|
245
|
+
if (req.method === 'GET') {
|
|
246
|
+
res.status(401).type('html').send(loginPageHtml(hasAccounts, tokenAllowed()));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
res.status(401).end();
|
|
250
|
+
};
|
|
251
|
+
function verifyWsClient(req) {
|
|
252
|
+
return !!identify(req);
|
|
253
|
+
}
|
|
254
|
+
return { middleware, verifyWsClient, identify };
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* 跨平台打开默认浏览器到指定 URL(无外部依赖)
|
|
258
|
+
* 失败静默;此为便利功能,不影响服务启动。
|
|
259
|
+
*/
|
|
260
|
+
export function openBrowser(url, proc) {
|
|
261
|
+
try {
|
|
262
|
+
const spawnDetached = (cmd, args) => {
|
|
263
|
+
proc.spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();
|
|
264
|
+
};
|
|
265
|
+
if (process.platform === 'darwin') {
|
|
266
|
+
spawnDetached('open', [url]);
|
|
267
|
+
}
|
|
268
|
+
else if (process.platform === 'win32') {
|
|
269
|
+
spawnDetached('cmd', ['/c', 'start', '""', url]);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
spawnDetached('xdg-open', [url]);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
/* ignore */
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/gate.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Context } from '@aalis/core';
|
|
2
|
+
import type { UserIdentity } from '@aalis/plugin-authority-api';
|
|
3
|
+
/** 从请求解析调用者身份(auth.ts 的 AuthSystem.identify) */
|
|
4
|
+
type IdentifyFn = (req: {
|
|
5
|
+
headers: {
|
|
6
|
+
cookie?: string;
|
|
7
|
+
};
|
|
8
|
+
}) => UserIdentity | undefined;
|
|
9
|
+
interface GateRequest {
|
|
10
|
+
headers: {
|
|
11
|
+
cookie?: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
interface GateResponse {
|
|
15
|
+
status(code: number): {
|
|
16
|
+
json(body: unknown): unknown;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
type GateMiddleware = (req: GateRequest, res: GateResponse, next: () => void) => void;
|
|
20
|
+
/**
|
|
21
|
+
* REST 路由权限闸工厂。
|
|
22
|
+
*
|
|
23
|
+
* 连接级认证(auth middleware)只回答"是否登录";本闸回答"该身份能否做这件事"——
|
|
24
|
+
* 每条 REST 路由声明一个 capability 与基础等级门槛,过 authorize 统一闸
|
|
25
|
+
* (与 tool/command/page-action 同一裁决:permissionPolicy > deny > grant > 角色链,
|
|
26
|
+
* 因此可对单个账户 grant `webui:files:read` 这类细粒度放行)。
|
|
27
|
+
*
|
|
28
|
+
* 分层缺省(可经 config.permissionAuthority 或 per-user grant/deny 调整):
|
|
29
|
+
* - 1:公共读(status / pages / 模型列表等,聊天面板可用即可)
|
|
30
|
+
* - 4:管理读(插件清单含原始配置、日志、文件浏览——可能含密钥/敏感信息)
|
|
31
|
+
* - 'owner':变更操作(配置写、插件启停装卸、服务偏好、文件删改)
|
|
32
|
+
*
|
|
33
|
+
* authority 服务缺席时放行(与 tools/commands 守卫缺席语义一致)。
|
|
34
|
+
*/
|
|
35
|
+
export declare function createRouteGate(ctx: Context, identify: IdentifyFn): (capability: string, declared: number | "owner") => GateMiddleware;
|
|
36
|
+
/** createRouteGate 返回的闸生成函数类型(传给各 routes/ 注册器) */
|
|
37
|
+
export type RouteGate = ReturnType<typeof createRouteGate>;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAElF,gDAAgD;AAChD,KAAK,UAAU,GAAG,CAAC,GAAG,EAAE;IAAE,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAAK,YAAY,GAAG,SAAS,CAAC;AAKtF,UAAU,WAAW;IACnB,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9B;AACD,UAAU,YAAY;IACpB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;KAAE,CAAC;CACxD;AACD,KAAK,cAAc,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAEtF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,IAIxD,YAAY,MAAM,EAAE,UAAU,MAAM,GAAG,OAAO,KAAG,cAAc,CAuBxE;AAED,kDAAkD;AAClD,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC"}
|
package/dist/gate.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST 路由权限闸工厂。
|
|
3
|
+
*
|
|
4
|
+
* 连接级认证(auth middleware)只回答"是否登录";本闸回答"该身份能否做这件事"——
|
|
5
|
+
* 每条 REST 路由声明一个 capability 与基础等级门槛,过 authorize 统一闸
|
|
6
|
+
* (与 tool/command/page-action 同一裁决:permissionPolicy > deny > grant > 角色链,
|
|
7
|
+
* 因此可对单个账户 grant `webui:files:read` 这类细粒度放行)。
|
|
8
|
+
*
|
|
9
|
+
* 分层缺省(可经 config.permissionAuthority 或 per-user grant/deny 调整):
|
|
10
|
+
* - 1:公共读(status / pages / 模型列表等,聊天面板可用即可)
|
|
11
|
+
* - 4:管理读(插件清单含原始配置、日志、文件浏览——可能含密钥/敏感信息)
|
|
12
|
+
* - 'owner':变更操作(配置写、插件启停装卸、服务偏好、文件删改)
|
|
13
|
+
*
|
|
14
|
+
* authority 服务缺席时放行(与 tools/commands 守卫缺席语义一致)。
|
|
15
|
+
*/
|
|
16
|
+
export function createRouteGate(ctx, identify) {
|
|
17
|
+
// 公共读基线:authority 缺席时,门槛 <= 此值的路由仍放行(聊天面板可用即可),
|
|
18
|
+
// 高于此值的(管理读/变更)一律拒绝——fail-closed,不在权限服务缺位时裸奔。
|
|
19
|
+
const PUBLIC_BASELINE = 1;
|
|
20
|
+
return (capability, declared) => (req, res, next) => {
|
|
21
|
+
const ownerLevel = ctx.config.get('ownerAuthority') ?? 5;
|
|
22
|
+
const declaredAuthority = declared === 'owner' ? ownerLevel : declared;
|
|
23
|
+
const authority = ctx.getService('authority');
|
|
24
|
+
if (!authority) {
|
|
25
|
+
// authority 缺席(未配置/启动失败/bounce 窗口期)→ 无法裁决等级。
|
|
26
|
+
// 公共读放行,其余 503(区别于 403:服务暂不可用,非权限不足)。
|
|
27
|
+
if (declaredAuthority > PUBLIC_BASELINE) {
|
|
28
|
+
res.status(503).json({ error: '权限服务不可用,敏感操作暂时被拒绝' });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
next();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const caller = identify(req) ?? { platform: 'webui', userId: 'console' };
|
|
35
|
+
const denied = authority.authorize(caller, { capabilities: [capability], declaredAuthority });
|
|
36
|
+
if (denied) {
|
|
37
|
+
res.status(403).json({ error: denied });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
next();
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=gate.js.map
|
package/dist/gate.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY,EAAE,QAAoB;IAChE,gDAAgD;IAChD,8CAA8C;IAC9C,MAAM,eAAe,GAAG,CAAC,CAAC;IAC1B,OAAO,CAAC,UAAkB,EAAE,QAA0B,EAAkB,EAAE,CACxE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACzD,MAAM,iBAAiB,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;QACvE,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,CAAmB,WAAW,CAAC,CAAC;QAChE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,6CAA6C;YAC7C,sCAAsC;YACtC,IAAI,iBAAiB,GAAG,eAAe,EAAE,CAAC;gBACxC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;gBACrD,OAAO;YACT,CAAC;YACD,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QACzE,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,YAAY,EAAE,CAAC,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAC9F,IAAI,MAAM,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;YACxC,OAAO;QACT,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACN,CAAC"}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,eAAO,MAAM,IAAI,EAAE,aAmClB,CAAC"}
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const i18n = {
|
|
2
|
+
'zh-CN': {
|
|
3
|
+
'plugin.webuiServer.displayName': 'WebUI 服务端',
|
|
4
|
+
'webui.page.dashboard': '仪表盘',
|
|
5
|
+
'webui.page.marketplace': '插件市场',
|
|
6
|
+
'webui.page.pluginConfig': '插件配置',
|
|
7
|
+
'webui.page.platforms': '平台接入',
|
|
8
|
+
'webui.page.files': '文件管理',
|
|
9
|
+
'webui.page.logs': '日志',
|
|
10
|
+
'config.webui.port.label': '端口',
|
|
11
|
+
'config.webui.port.description': 'Web 管理界面的 HTTP 端口',
|
|
12
|
+
'config.webui.host.label': '监听地址',
|
|
13
|
+
'config.webui.host.description': '绑定的 IP 地址,0.0.0.0 可对外访问',
|
|
14
|
+
'config.webui.fileRoot.label': '文件浏览根',
|
|
15
|
+
'config.webui.fileRoot.description': '文件管理页面使用的 storage 根 ID,默认 workspace',
|
|
16
|
+
'config.webui.workspaceRoot.label': '兼容文件根目录',
|
|
17
|
+
'config.webui.workspaceRoot.description': '缺少 storage 服务时的兼容文件根目录',
|
|
18
|
+
},
|
|
19
|
+
'en-US': {
|
|
20
|
+
'plugin.webuiServer.displayName': 'WebUI Server',
|
|
21
|
+
'webui.page.dashboard': 'Dashboard',
|
|
22
|
+
'webui.page.marketplace': 'Marketplace',
|
|
23
|
+
'webui.page.pluginConfig': 'Plugin Config',
|
|
24
|
+
'webui.page.platforms': 'Platforms',
|
|
25
|
+
'webui.page.files': 'Files',
|
|
26
|
+
'webui.page.logs': 'Logs',
|
|
27
|
+
'config.webui.port.label': 'Port',
|
|
28
|
+
'config.webui.port.description': 'HTTP port for the Web management UI.',
|
|
29
|
+
'config.webui.host.label': 'Bind Address',
|
|
30
|
+
'config.webui.host.description': 'IP address to bind. Use 0.0.0.0 to allow external access.',
|
|
31
|
+
'config.webui.fileRoot.label': 'File Root',
|
|
32
|
+
'config.webui.fileRoot.description': 'Storage root ID used by the file manager. Defaults to workspace.',
|
|
33
|
+
'config.webui.workspaceRoot.label': 'Compatibility File Root',
|
|
34
|
+
'config.webui.workspaceRoot.description': 'Fallback file root used when the storage service is unavailable.',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=i18n.js.map
|
package/dist/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,IAAI,GAAkB;IACjC,OAAO,EAAE;QACP,gCAAgC,EAAE,WAAW;QAC7C,sBAAsB,EAAE,KAAK;QAC7B,wBAAwB,EAAE,MAAM;QAChC,yBAAyB,EAAE,MAAM;QACjC,sBAAsB,EAAE,MAAM;QAC9B,kBAAkB,EAAE,MAAM;QAC1B,iBAAiB,EAAE,IAAI;QACvB,yBAAyB,EAAE,IAAI;QAC/B,+BAA+B,EAAE,mBAAmB;QACpD,yBAAyB,EAAE,MAAM;QACjC,+BAA+B,EAAE,yBAAyB;QAC1D,6BAA6B,EAAE,OAAO;QACtC,mCAAmC,EAAE,qCAAqC;QAC1E,kCAAkC,EAAE,SAAS;QAC7C,wCAAwC,EAAE,wBAAwB;KACnE;IACD,OAAO,EAAE;QACP,gCAAgC,EAAE,cAAc;QAChD,sBAAsB,EAAE,WAAW;QACnC,wBAAwB,EAAE,aAAa;QACvC,yBAAyB,EAAE,eAAe;QAC1C,sBAAsB,EAAE,WAAW;QACnC,kBAAkB,EAAE,OAAO;QAC3B,iBAAiB,EAAE,MAAM;QACzB,yBAAyB,EAAE,MAAM;QACjC,+BAA+B,EAAE,sCAAsC;QACvE,yBAAyB,EAAE,cAAc;QACzC,+BAA+B,EAAE,2DAA2D;QAC5F,6BAA6B,EAAE,WAAW;QAC1C,mCAAmC,EAAE,kEAAkE;QACvG,kCAAkC,EAAE,yBAAyB;QAC7D,wCAAwC,EAAE,kEAAkE;KAC7G;CACF,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ConfigSchema, Context } from '@aalis/core';
|
|
2
|
+
export declare const name = "@aalis/plugin-webui-server";
|
|
3
|
+
export declare const displayName = "WebUI \u670D\u52A1\u7AEF";
|
|
4
|
+
export declare const subsystem = "platform";
|
|
5
|
+
export declare const provides: string[];
|
|
6
|
+
export declare const inject: {
|
|
7
|
+
optional: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare const configSchema: ConfigSchema;
|
|
10
|
+
export declare const defaultConfig: {
|
|
11
|
+
port: number;
|
|
12
|
+
host: string;
|
|
13
|
+
fileRoot: string;
|
|
14
|
+
autoOpen: boolean;
|
|
15
|
+
tokenMode: string;
|
|
16
|
+
fixedToken: string;
|
|
17
|
+
relationGraphDefaultSpacing: number;
|
|
18
|
+
marketplaceRegistry: string;
|
|
19
|
+
};
|
|
20
|
+
export { type WSIncoming, WSIncomingSchema } from './protocol.js';
|
|
21
|
+
export declare function apply(ctx: Context, config: Record<string, unknown>): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAc,YAAY,EAAE,OAAO,EAAkC,MAAM,aAAa,CAAC;AAqCrG,eAAO,MAAM,IAAI,+BAA+B,CAAC;AACjD,eAAO,MAAM,WAAW,6BAAc,CAAC;AACvC,eAAO,MAAM,SAAS,aAAa,CAAC;AACpC,eAAO,MAAM,QAAQ,UAAmB,CAAC;AACzC,eAAO,MAAM,MAAM;;CAMlB,CAAC;AAWF,eAAO,MAAM,YAAY,EAAE,YAgD1B,CAAC;AAEF,eAAO,MAAM,aAAa;;;;;;;;;CASzB,CAAC;AAoBF,OAAO,EAAE,KAAK,UAAU,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAqIlE,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAg0CxF"}
|