ework-web 0.10.29 → 0.10.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.29",
3
+ "version": "0.10.30",
4
4
  "type": "module",
5
5
  "description": "ework-web — standalone multi-project issue tracker. Local SQLite-backed, no external API dependency. Bun + TypeScript + SSR HTML.",
6
6
  "license": "MIT",
package/src/auth.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Config } from "./config";
2
2
  import { getUserByLogin, ensureUser, verifyPat, type UserRow } from "./store";
3
3
  import { runAuthHook } from "./auth-hook";
4
+ import { readFileSync, existsSync } from "fs";
4
5
 
5
6
  // Per-user token-cookie auth. Cookie value is HMAC-signed and carries login +
6
7
  // issued-at, so the server is stateless (no session table). Two cookie formats
@@ -191,7 +192,16 @@ function esc(s: string): string {
191
192
  .replace(/"/g, """);
192
193
  }
193
194
 
194
- export function loginHTML(next: string, error?: string): string {
195
+ export function loginHTML(next: string, error?: string, cfg?: Config): string {
196
+ if (cfg?.loginPage && existsSync(cfg.loginPage)) {
197
+ try {
198
+ const tpl = readFileSync(cfg.loginPage, "utf-8");
199
+ return tpl
200
+ .replace(/\{\{next\}\}/g, esc(sanitizeNext(next)))
201
+ .replace(/\{\{error\}\}/g, error ? esc(error) : "");
202
+ } catch {
203
+ }
204
+ }
195
205
  const err = error ? `<div class="err">${esc(error)}</div>` : "";
196
206
  return `<!doctype html><html lang="zh"><head><meta charset="utf-8">
197
207
  <meta name="viewport" content="width=device-width,initial-scale=1">
package/src/config.ts CHANGED
@@ -81,6 +81,7 @@ export const configSchema = z.object({
81
81
  routerAdminToken: z.string().default(""),
82
82
  internalAuthHook: z.string().default(""),
83
83
  userAuthHook: z.string().default(""),
84
+ loginPage: z.string().default(""),
84
85
  // Default "provider/model" string passed to `opencode run --model <X>`.
85
86
  // Empty = let opencode pick per its own opencode.json + env. ework-daemon
86
87
  // pushes this (or the per-project override) on every spawn to defend
@@ -183,6 +184,7 @@ export async function loadConfig(): Promise<Config> {
183
184
  routerAdminToken: process.env.WORK_ROUTER_ADMIN_TOKEN ?? "",
184
185
  internalAuthHook: process.env.WORK_INTERNAL_AUTH_HOOK ?? "",
185
186
  userAuthHook: process.env.WORK_USER_AUTH_HOOK ?? "",
187
+ loginPage: process.env.WORK_LOGIN_PAGE ?? "",
186
188
  defaultModel: db.defaultModel ?? process.env.WORK_DEFAULT_MODEL,
187
189
  autowireActive: process.env.WORK_AUTOWIRE_ACTIVE !== "false",
188
190
  webhookMaxConcurrent: Number(process.env.WORK_WEBHOOK_MAX_CONCURRENT ?? "6"),
package/src/index.ts CHANGED
@@ -481,7 +481,7 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
481
481
  const form = await req.formData().catch(() => new FormData());
482
482
  const next = sanitizeNext(String(form.get("next") ?? "/"));
483
483
  if (!rateLimit(`login:${ip}`, 5, 5 / (15 * 60))) {
484
- return html(loginHTML(next, "尝试过多,15 分钟后再试"), 429);
484
+ return html(loginHTML(next, "尝试过多,15 分钟后再试", cfg), 429);
485
485
  }
486
486
  const login = String(form.get("login") ?? "").trim();
487
487
  const password = String(form.get("password") ?? "");
@@ -516,10 +516,10 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
516
516
  : login || password
517
517
  ? "用户名或密码错误"
518
518
  : "请填写用户名密码或共享 token";
519
- return html(loginHTML(next, err), 401);
519
+ return html(loginHTML(next, err, cfg), 401);
520
520
  }
521
521
  const next = sanitizeNext(url.searchParams.get("next") ?? "/");
522
- return html(loginHTML(next));
522
+ return html(loginHTML(next, undefined, cfg));
523
523
  }
524
524
 
525
525
  if (url.pathname === "/logout" && req.method === "POST") {