1
0

Cleanup of unused code, moving mounts to plug

This commit is contained in:
Zef Hemel 2022-07-11 13:14:31 +02:00
parent bd462e3d42
commit 8d61812d0e
6 changed files with 1 additions and 429 deletions

View File

@ -280,30 +280,3 @@ functions:
name: "UI: Hide BHS" name: "UI: Hide BHS"
key: "Ctrl-Alt-b" key: "Ctrl-Alt-b"
mac: "Cmd-Alt-b" mac: "Cmd-Alt-b"
# Mounting
readPageMounted:
path: ./mount.ts:readPageMounted
pageNamespace:
pattern: "^🚪 .+"
operation: readPage
writePageMounted:
path: ./mount.ts:writePageMounted
pageNamespace:
pattern: "^🚪 .+"
operation: writePage
deletePageMounted:
path: ./mount.ts:deletePageMounted
pageNamespace:
pattern: "^🚪 .+"
operation: deletePage
getPageMetaMounted:
path: ./mount.ts:getPageMetaMounted
pageNamespace:
pattern: "^🚪 .+"
operation: getPageMeta
listPagesMounted:
path: ./mount.ts:listPagesMounted
pageNamespace:
pattern: "^🚪 .+"
operation: listPages

View File

@ -1,296 +0,0 @@
import { PageMeta } from "@silverbulletmd/common/types";
import {
deleteFile,
getFileMeta,
listFiles,
readFile,
writeFile,
} from "@plugos/plugos-syscall/fs";
import { parseMarkdown } from "@silverbulletmd/plugos-silverbullet-syscall/markdown";
import {
findNodeOfType,
renderToText,
replaceNodesMatching,
} from "@silverbulletmd/common/tree";
import { readPage } from "@silverbulletmd/plugos-silverbullet-syscall/space";
import YAML from "yaml";
const globalMountPrefix = "🚪 ";
type MountPoint = {
prefix: string;
path: string;
password?: string;
protocol: "file" | "http";
perm: "rw" | "ro";
};
let mountPointCache: MountPoint[] = [];
async function updateMountPoints() {
let mountPointsText = "";
try {
let { text } = await readPage("MOUNTS");
mountPointsText = text;
} catch {
// No MOUNTS file, so that's all folks!
mountPointCache = [];
return;
}
let tree = await parseMarkdown(mountPointsText);
let codeTextNode = findNodeOfType(tree, "CodeText");
if (!codeTextNode) {
console.error("Could not find yaml block in MOUNTS");
return;
}
let mountsYaml = codeTextNode.children![0].text;
let mountList: Partial<MountPoint>[] = YAML.parse(mountsYaml!);
if (!Array.isArray(mountList)) {
console.error("Invalid MOUNTS file, should have array of mount points");
return;
}
// Let's validate this and fill in some of the blanks
for (let mountPoint of mountList) {
if (!mountPoint.prefix) {
console.error("Invalid mount point, no prefix specified", mountPoint);
return;
}
if (!mountPoint.path) {
console.error("Invalid mount point, no path specified", mountPoint);
return;
}
if (!mountPoint.perm) {
mountPoint.perm = "rw";
}
if (mountPoint.path.startsWith("file:")) {
mountPoint.protocol = "file";
mountPoint.path = mountPoint.path.substring("file:".length);
} else {
mountPoint.protocol = "http";
mountPoint.path += "/fs"; // Add the /fs suffix to the path
}
}
mountPointCache = mountList as MountPoint[];
}
async function translateLinksWithPrefix(
text: string,
prefix: string
): Promise<string> {
prefix = `${globalMountPrefix}${prefix}`;
let tree = await parseMarkdown(text);
replaceNodesMatching(tree, (tree) => {
if (tree.type === "WikiLinkPage") {
// Add the prefix in the link text
tree.children![0].text = prefix + tree.children![0].text;
}
return undefined;
});
text = renderToText(tree);
return text;
}
async function translateLinksWithoutPrefix(text: string, prefix: string) {
prefix = `${globalMountPrefix}${prefix}`;
let tree = await parseMarkdown(text);
replaceNodesMatching(tree, (tree) => {
if (tree.type === "WikiLinkPage") {
// Remove the prefix in the link text
let text = tree.children![0].text!;
if (text.startsWith(prefix)) {
tree.children![0].text = text.substring(prefix.length);
}
}
return undefined;
});
return renderToText(tree);
}
function lookupMountPoint(fullPath: string): {
resolvedPath: string;
mountPoint: MountPoint;
} {
fullPath = fullPath.substring(globalMountPrefix.length);
for (let mp of mountPointCache) {
if (fullPath.startsWith(mp.prefix)) {
return {
resolvedPath: `${mp.path}/${fullPath.substring(mp.prefix.length)}`,
mountPoint: mp,
};
}
}
throw new Error("No mount point found for " + fullPath);
}
export async function readPageMounted(
name: string
): Promise<{ text: string; meta: PageMeta }> {
await updateMountPoints();
let { resolvedPath, mountPoint } = lookupMountPoint(name);
if (mountPoint.protocol === "file") {
let { text, meta } = await readFile(`${resolvedPath}.md`);
return {
text: await translateLinksWithPrefix(text, mountPoint.prefix),
meta: {
name: name,
lastModified: meta.lastModified,
perm: mountPoint.perm,
},
};
} else {
let res = await fetch(resolvedPath, {
method: "GET",
headers: authHeaders(mountPoint),
});
if (res.status !== 200) {
throw new Error(
`Failed to read page: ${res.status}: ${await res.text()}`
);
}
if (res.headers.get("X-Status") === "404") {
throw new Error(`Page not found`);
}
let text = await res.text();
return {
text: await translateLinksWithPrefix(text, mountPoint.prefix),
meta: {
name: name,
lastModified: +(res.headers.get("Last-Modified") || "0"),
perm: (res.headers.get("X-Permission") as "rw" | "ro") || "rw",
},
};
}
}
export async function writePageMounted(
name: string,
text: string
): Promise<PageMeta> {
await updateMountPoints();
let { resolvedPath, mountPoint } = lookupMountPoint(name);
text = await translateLinksWithoutPrefix(text, mountPoint.prefix);
if (mountPoint.protocol === "file") {
let meta = await writeFile(`${resolvedPath}.md`, text);
return {
name: name,
lastModified: meta.lastModified,
perm: mountPoint.perm,
};
} else {
let res = await fetch(resolvedPath, {
method: "PUT",
headers: authHeaders(mountPoint),
body: text,
});
if (res.status !== 200) {
throw new Error(
`Failed to write page: ${res.status}: ${await res.text()}`
);
}
return {
name: name,
lastModified: +(res.headers.get("Last-Modified") || "0"),
perm: (res.headers.get("X-Permission") as "rw" | "ro") || "rw",
};
}
}
export async function deletePageMounted(name: string): Promise<void> {
await updateMountPoints();
let { resolvedPath, mountPoint } = lookupMountPoint(name);
if (mountPoint.protocol === "file") {
if (mountPoint.perm === "rw") {
await deleteFile(`${resolvedPath}.md`);
} else {
throw new Error("Deleting read-only page");
}
} else {
throw new Error("Not yet implemented");
}
}
function authHeaders(mountPoint: MountPoint): HeadersInit {
return {
Authorization: mountPoint.password ? `Bearer ${mountPoint.password}` : "",
};
}
export async function getPageMetaMounted(name: string): Promise<PageMeta> {
await updateMountPoints();
let { resolvedPath, mountPoint } = lookupMountPoint(name);
if (mountPoint.protocol === "file") {
let meta = await getFileMeta(`${resolvedPath}.md`);
return {
name,
lastModified: meta.lastModified,
perm: mountPoint.perm,
};
} else {
// http/https
let res = await fetch(resolvedPath, {
method: "OPTIONS",
headers: authHeaders(mountPoint),
});
if (res.status !== 200) {
throw new Error(
`Failed to list pages: ${res.status}: ${await res.text()}`
);
}
if (res.headers.get("X-Status") === "404") {
throw new Error(`Page not found`);
}
return {
name: name,
lastModified: +(res.headers.get("Last-Modified") || "0"),
perm: (res.headers.get("X-Permission") as "rw" | "ro") || "rw",
};
}
}
export async function listPagesMounted(): Promise<PageMeta[]> {
await updateMountPoints();
let allPages: PageMeta[] = [];
for (let mp of mountPointCache) {
if (mp.protocol === "file") {
let files = await listFiles(mp.path, true);
for (let file of files) {
if (!file.name.endsWith(".md")) {
continue;
}
allPages.push({
name: `${globalMountPrefix}${mp.prefix}${file.name.substring(
0,
file.name.length - 3
)}`,
lastModified: file.lastModified,
perm: mp.perm,
});
}
} else {
let res = await fetch(mp.path, {
headers: authHeaders(mp),
});
if (res.status !== 200) {
throw new Error(
`Failed to list pages: ${res.status}: ${await res.text()}`
);
}
let remotePages: PageMeta[] = await res.json();
// console.log("Remote pages", remotePages);
for (let pageMeta of remotePages) {
if (pageMeta.name.startsWith("_")) {
continue;
}
allPages.push({
name: `${globalMountPrefix}${mp.prefix}${pageMeta.name}`,
lastModified: pageMeta.lastModified,
perm: mp.perm,
});
}
}
}
return allPages;
}

View File

@ -1,29 +0,0 @@
import { beforeEach, afterEach, expect, test } from "@jest/globals";
import { unlink } from "fs/promises";
import knex, { Knex } from "knex";
import { Authenticator } from "./auth";
let db: Knex<any, unknown[]> | undefined;
beforeEach(async () => {
db = knex({
client: "better-sqlite3",
connection: {
filename: "auth-test.db",
},
useNullAsDefault: true,
});
});
afterEach(async () => {
db!.destroy();
await unlink("auth-test.db");
});
test("Test auth", async () => {
let auth = new Authenticator(db!);
await auth.ensureTables();
await auth.createAccount("admin", "admin");
expect(await auth.verify("admin", "admin")).toBe(true);
expect(await auth.verify("admin", "sup")).toBe(false);
});

View File

@ -1,71 +0,0 @@
import * as crypto from "crypto";
import { Knex } from "knex";
import { promisify } from "util";
const pbkdf2 = promisify(crypto.pbkdf2);
type Account = {
username: string;
hashed_password: any;
salt: any;
};
export class Authenticator {
tableName = "tokens";
constructor(private db: Knex<any, unknown[]>) {}
middleware(req: any, res: any, next: any) {
console.log("GOing through here", req.headers.authorization);
// if (req.headers)
next();
}
async ensureTables() {
if (!(await this.db.schema.hasTable(this.tableName))) {
await this.db.schema.createTable(this.tableName, (table) => {
table.string("username");
table.binary("hashed_password");
table.binary("salt");
table.primary(["username"]);
});
// await this.createAccount("admin", "admin");
console.log(`Created table ${this.tableName}`);
}
}
async createAccount(username: string, password: string) {
var salt = crypto.randomBytes(16);
let encryptedPassword = await pbkdf2(password, salt, 310000, 32, "sha256");
await this.db<Account>(this.tableName).insert({
username,
hashed_password: encryptedPassword,
salt,
});
}
async updatePassword(username: string, password: string) {
var salt = crypto.randomBytes(16);
let encryptedPassword = await pbkdf2(password, salt, 310000, 32, "sha256");
await this.db<Account>(this.tableName).update({
username,
hashed_password: encryptedPassword,
salt,
});
}
async verify(username: string, password: string): Promise<boolean> {
let users = await this.db<Account>(this.tableName).where({ username });
if (users.length === 0) {
throw new Error(`No such user: ${username}`);
}
let user = users[0];
let encryptedPassword = await pbkdf2(
password,
user.salt,
310000,
32,
"sha256"
);
return crypto.timingSafeEqual(user.hashed_password, encryptedPassword);
}
}

View File

@ -29,7 +29,6 @@ import { esbuildSyscalls } from "@plugos/plugos/syscalls/esbuild";
import { systemSyscalls } from "./syscalls/system"; import { systemSyscalls } from "./syscalls/system";
import { plugPrefix } from "@silverbulletmd/common/spaces/constants"; import { plugPrefix } from "@silverbulletmd/common/spaces/constants";
import { Authenticator } from "./auth";
import sandboxSyscalls from "@plugos/plugos/syscalls/sandbox"; import sandboxSyscalls from "@plugos/plugos/syscalls/sandbox";
// import globalModules from "../common/dist/global.plug.json"; // import globalModules from "../common/dist/global.plug.json";
@ -265,8 +264,6 @@ export class ExpressServer {
await ensureFTSTable(this.db, "fts"); await ensureFTSTable(this.db, "fts");
await this.ensureIndexPage(); await this.ensureIndexPage();
let auth = new Authenticator(this.db);
// Serve static files (javascript, css, html) // Serve static files (javascript, css, html)
this.app.use("/", express.static(this.distDir)); this.app.use("/", express.static(this.distDir));

View File

@ -27,9 +27,7 @@
] ]
}, },
"test": { "test": {
"source": [ "source": [],
"auth.test.ts"
],
"outputFormat": "commonjs", "outputFormat": "commonjs",
"isLibrary": true, "isLibrary": true,
"context": "node" "context": "node"