fix(database): use per-user admin passwords

This commit is contained in:
Codex committed 2026-09-28 19:03:19 +08:00
1 parent 33e80a566e
commit 9862348d70
17 files changed
+188 -38

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ const meta = new URL("../dist/.openai/", import.meta.url);
await rm(dist, { recursive: true, force: true });
await Promise.all([mkdir(server, { recursive: true }), mkdir(meta, { recursive: true })]);
const [rawIndexHtml, addonsJs, bootGuardJs, i18nJs, tasksJs, calendarJs, i18nRenderersJs, desktopPersonalizationJs, desktopWidgetsJs, browserAppJs, dataActionsJs, terminalAppJs, dbAdminJs, manifest, serviceWorker, icon, localeEn, localeZh] = await Promise.all([text("index.html"), text("addons.js"), text("boot-guard.js"), text("i18n.js"), text("tasks.js"), text("calendar.js"), text("i18n-renderers.js"), text("desktop-personalization.js"), text("desktop-widgets.js"), text("browser-app.js"), text("data-actions.js"), text("terminal-app.js"), text("db-admin.js"), text("manifest.webmanifest"), text("service-worker.js"), text("life-os-icon.svg"), readFile(new URL("../public/locales/en.json", import.meta.url), "utf8"), readFile(new URL("../public/locales/zh-CN.json", import.meta.url), "utf8")]);
const release = "1.14.0";
const release = "1.14.1";
const localeEnJs = `window.LifeOSLocales=window.LifeOSLocales||{};window.LifeOSLocales.en=${localeEn};`;
const localeZhJs = `window.LifeOSLocales=window.LifeOSLocales||{};window.LifeOSLocales['zh-CN']=${localeZh};`;
let indexHtml = rawIndexHtml.replace('src="addons.js"', `src="addons.js?v=${release}"`).replace('src="tasks.js"', `src="tasks.js?v=${release}"`).replace('src="calendar.js"', `src="calendar.js?v=${release}"`);
+22 -2
View File
@@ -9,6 +9,7 @@ sqlite.exec("PRAGMA foreign_keys = ON");
sqlite.exec(readFileSync(new URL("../drizzle/0000_lifeos_cloud_state.sql", import.meta.url), "utf8"));
sqlite.exec(readFileSync(new URL("../drizzle/0001_lifeos_records.sql", import.meta.url), "utf8"));
sqlite.exec(readFileSync(new URL("../drizzle/0002_lifeos_db_admin.sql", import.meta.url), "utf8"));
sqlite.exec(readFileSync(new URL("../drizzle/0003_lifeos_admin_credentials.sql", import.meta.url), "utf8"));
const db = {
prepare(sql) {
return {
@@ -37,7 +38,7 @@ async function call(path, method = "GET", data, as = headers) {
const response = await worker.fetch(new Request("https://life-os.test" + path, {
method, headers: { ...as, "content-type": "application/json" },
body: data === undefined ? undefined : JSON.stringify(data)
}), { DB: db, LIFEOS_DB_ADMIN_PASSWORD: "local test password with 28 chars" });
}), { DB: db });
return { status: response.status, body: await response.json() };
}
@@ -104,6 +105,15 @@ assert.deepEqual(Array.from(pending, item => item.kind), ["tasks"], "editing a t
await client.window.CloudTest.sync();
assert.equal((await call("/api/lifeos-state")).body.state.system.tasksBoard.tasks[0].done, true);
assert.equal((await call("/api/db-admin/tables/tasks/rows")).status, 401);
assert.equal((await call("/api/db-admin/config")).body.configured, false);
assert.equal((await call("/api/db-admin/login", "POST", { username: "lifeos-admin", password: "local test password with 28 chars", acknowledged: true })).status, 409);
const setup = await call("/api/db-admin/setup", "POST", {
username: "lifeos-admin", password: "local test password with 28 chars", acknowledged: true
});
assert.equal(setup.status, 200);
assert.equal((await call("/api/db-admin/config")).body.configured, true);
assert.equal((await call("/api/db-admin/setup", "POST", { username: "lifeos-admin", password: "replacement password 123", acknowledged: true })).status, 409);
assert.equal((await call("/api/db-admin/tables/tasks/rows", "GET", undefined, { ...headers, "x-lifeos-admin-session": setup.body.token })).status, 200);
assert.equal((await call("/api/db-admin/login", "POST", { username: "lifeos-admin", password: "bad", acknowledged: true })).status, 401);
const login = await call("/api/db-admin/login", "POST", {
username: "lifeos-admin", password: "local test password with 28 chars", acknowledged: true
@@ -115,12 +125,22 @@ let admin = await call("/api/db-admin/tables/tasks/rows", "GET", undefined, auth
assert.equal(admin.status, 200);
assert.equal(admin.body.total, 2);
assert.equal((await call("/api/db-admin/tables/tasks/rows", "GET", undefined, { ...auth, "oai-authenticated-user-id": "bob" })).status, 401, "admin session is tied to one user");
assert.equal((await call("/api/db-admin/config", "GET", undefined, { "oai-authenticated-user-id": "bob" })).body.configured, false);
assert.equal((await call("/api/db-admin/setup", "POST", { username: "lifeos-admin", password: "bob account password 67890", acknowledged: true }, { "oai-authenticated-user-id": "bob" })).status, 200);
assert.equal((await call("/api/db-admin/login", "POST", { username: "lifeos-admin", password: "bob account password 67890", acknowledged: true })).status, 401, "another user's password cannot unlock Alice");
admin = await call("/api/db-admin/tables/tasks/rows/t3", "POST", { baseVersion: 0, value: { id: "t3", title: "From database" } }, auth);
assert.equal(admin.body.version, 1);
admin = await call("/api/db-admin/tables/tasks/rows/t3", "PATCH", { baseVersion: 1, value: { id: "t3", title: "Edited" } }, auth);
assert.equal(admin.body.version, 2);
assert.equal((await call("/api/db-admin/tables/tasks/rows/t3", "PATCH", { baseVersion: 1, value: { id: "t3", title: "Stale" } }, auth)).status, 409);
assert.equal((await call("/api/db-admin/tables/tasks/rows/t3?version=2", "DELETE", undefined, auth)).status, 200);
assert.equal((await call("/api/db-admin/logout", "POST", {}, auth)).status, 200);
assert.equal((await call("/api/db-admin/password", "POST", { currentPassword: "wrong password 123", newPassword: "new secure password 12345" }, auth)).status, 401);
const rotated = await call("/api/db-admin/password", "POST", {
currentPassword: "local test password with 28 chars", newPassword: "new secure password 12345"
}, auth);
assert.equal(rotated.status, 200);
assert.equal((await call("/api/db-admin/tables/tasks/rows", "GET", undefined, auth)).status, 401);
assert.equal((await call("/api/db-admin/login", "POST", { username: "lifeos-admin", password: "local test password with 28 chars", acknowledged: true })).status, 401);
assert.equal((await call("/api/db-admin/login", "POST", { username: "lifeos-admin", password: "new secure password 12345", acknowledged: true })).status, 200);
assert.equal((await call("/api/db-admin/logout", "POST", {}, { ...headers, "x-lifeos-admin-session": rotated.body.token })).status, 200);
console.log("Life OS record migration, sync conflict, tags, and user isolation valid");
+5 -2
View File
@@ -58,12 +58,15 @@ for (const script of [assets.i18nJs, assets.dataActionsJs, assets.terminalAppJs,
const adminNode = { innerHTML: "", querySelector: () => ({ addEventListener() {} }) };
const adminContext = vm.createContext({
document: { createElement: () => ({ textContent: "" }), head: { append() {} }, querySelector: () => adminNode },
LifeI18n: { t: key => key }, window: {}, Date
LifeI18n: { t: key => key }, window: {}, Date,
fetch: async () => ({ ok: true, status: 200, json: async () => ({ configured: false }) })
});
vm.runInContext(assets.dbAdminJs, adminContext);
adminContext.window.databaseApp(adminNode);
await new Promise(resolve => setTimeout(resolve, 0));
assert.match(adminNode.innerHTML, /lifeos-admin/);
assert.match(adminNode.innerHTML, /type="password"/);
assert.match(adminNode.innerHTML, /confirmPassword/);
assert.match(adminNode.innerHTML, /name="acknowledged" required/);
const browserNodes = Object.fromEntries(['.browser-toolbar', '.browser-address', 'iframe', '[data-action="back"]', '[data-action="forward"]', '.browser-external', '.browser-hint-link'].map(key => [key, { value: '', href: '', disabled: false, listeners: {}, addEventListener(type, handler) { this.listeners[type] = handler; }, getAttribute(name) { return this[name] ?? null; } }]));
const browserContainer = { dataset: {}, innerHTML: '', querySelector(selector) { return selector === '.browser-shell' ? (this.innerHTML ? {} : null) : browserNodes[selector]; } };
@@ -183,7 +186,7 @@ assert.ok(command('date').message.length > 8);
assert.ok(command('time').message.includes(':'));
assert.ok(command('datetime').message.length > 12);
assert.equal(command('uptime').message, 'terminal.uptime');
assert.equal(command('version').message, 'Life OS v1.14.0');
assert.equal(command('version').message, 'Life OS v1.14.1');
assert.equal(command('status').message, 'terminal.status');
assert.match(command('apps').message, /calendar/);
assert.equal(command('echo "hello Life OS"').message, 'hello Life OS');