Cleanup of unused code, moving mounts to plug
This commit is contained in:
parent
bd462e3d42
commit
8d61812d0e
@ -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
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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);
|
|
||||||
});
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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));
|
||||||
|
|
||||||
|
@ -27,9 +27,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"source": [
|
"source": [],
|
||||||
"auth.test.ts"
|
|
||||||
],
|
|
||||||
"outputFormat": "commonjs",
|
"outputFormat": "commonjs",
|
||||||
"isLibrary": true,
|
"isLibrary": true,
|
||||||
"context": "node"
|
"context": "node"
|
||||||
|
Loading…
Reference in New Issue
Block a user