sync: update from private repo (88dd58b)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-09 10:50:27 +00:00
parent 2bab882d08
commit 2ec9853655
17 changed files with 1099 additions and 27 deletions
+45
View File
@@ -0,0 +1,45 @@
/**
* deleteUserFolder — removes a user's filesystem folder on account deletion.
*
* The DB cascade-deletes owned rows, but the user folder
* ({root}/{ownerId}/...) is on disk and was previously orphaned on delete.
* See docs/superpowers/plans/2026-06-09-local-auth.md.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { deleteUserFolder, ensureUserFolder } from './paths.js';
describe('deleteUserFolder', () => {
let root = '';
beforeEach(() => { root = mkdtempSync(join(tmpdir(), 'maestro-uf-')); });
afterEach(() => { if (root) { rmSync(root, { recursive: true, force: true }); root = ''; } });
it('removes the entire user folder', () => {
const dir = ensureUserFolder(root, 'alice');
writeFileSync(join(dir, 'notes', 'x.md'), 'hi');
expect(existsSync(dir)).toBe(true);
deleteUserFolder(root, 'alice');
expect(existsSync(dir)).toBe(false);
});
it('NEVER removes the local/system folder (shared no-auth owner)', () => {
const dir = ensureUserFolder(root, 'local');
deleteUserFolder(root, 'local');
expect(existsSync(dir)).toBe(true); // preserved
});
it('is a no-op when the folder does not exist', () => {
expect(() => deleteUserFolder(root, 'ghost')).not.toThrow();
});
it('refuses path-traversal ids (no escape outside root)', () => {
// userRoot rejects these; deleteUserFolder must not delete anything outside.
const sibling = mkdtempSync(join(tmpdir(), 'maestro-sibling-'));
writeFileSync(join(sibling, 'keep.txt'), 'keep');
expect(() => deleteUserFolder(root, '../../etc')).not.toThrow();
expect(existsSync(join(sibling, 'keep.txt'))).toBe(true);
rmSync(sibling, { recursive: true, force: true });
});
});
+27 -1
View File
@@ -1,6 +1,9 @@
import { mkdirSync, chmodSync, existsSync, readFileSync, writeFileSync, unlinkSync, statSync, openSync, readSync, closeSync } from 'fs';
import { mkdirSync, chmodSync, existsSync, readFileSync, writeFileSync, unlinkSync, statSync, openSync, readSync, closeSync, rmSync } from 'fs';
import { resolve, join, relative, isAbsolute } from 'path';
/** The shared no-auth / local-auth system owner. Its folder is never deleted. */
export const LOCAL_SYSTEM_OWNER_ID = 'local';
const USER_AGENTS_MAX_BYTES = 64 * 1024;
export const USER_SUBDIRS = ['scripts', 'browser-macros', 'templates', 'recordings', 'trash', 'memory', 'pets', 'notes'] as const;
@@ -29,6 +32,29 @@ export function ensureUserFolder(rootDir: string, ownerId: string): string {
return root;
}
/**
* Recursively remove a user's entire folder ({root}/{ownerId}). Called when an
* account is deleted so its scripts/macros/recordings/notes/memory/pets don't
* orphan on disk (the DB cascade-deletes rows but not files).
*
* Guards:
* - the `local` system owner is NEVER removed (shared no-auth / local-auth
* admin; deleting it would wipe the single-user deployment's data)
* - an invalid / traversal ownerId resolves to nothing and is a silent no-op
* (userRoot throws on '/', '\\', '\0', absolute paths)
* - missing folder is a no-op (rmSync force)
*/
export function deleteUserFolder(rootDir: string, ownerId: string): void {
if (ownerId === LOCAL_SYSTEM_OWNER_ID) return;
let root: string;
try {
root = userRoot(rootDir, ownerId);
} catch {
return; // invalid ownerId — never delete anything outside a valid user root
}
rmSync(root, { recursive: true, force: true });
}
export function resolveUserSubdir(
rootDir: string,
ownerId: string,