diff --git a/package.json b/package.json index 6a28a07..332ac6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hibas123/realtimedb", - "version": "2.0.0-beta.18", + "version": "2.0.0-beta.19", "description": "", "main": "lib/index.js", "private": true, diff --git a/src/database/query.ts b/src/database/query.ts index 1dfd4f5..3ca5bde 100644 --- a/src/database/query.ts +++ b/src/database/query.ts @@ -154,6 +154,7 @@ export abstract class Query { } else if (this.permission === "write" && !perm.write) { throw new QueryError("No permission!"); } + this.query.path = perm.path; return this._runner.call( this, collection, @@ -170,10 +171,12 @@ export abstract class Query { this.query.path, this.session ); - if (this.permission === "read" && !perm.read) { + if (!perm.read) { throw new QueryError("No permission!"); } + this.query.path = perm.path; + const receivedChanges = (changes: Change[]) => { let res = changes .filter(change => this.checkChange(change)) diff --git a/src/database/rules.ts b/src/database/rules.ts index e640d87..9f8c72e 100644 --- a/src/database/rules.ts +++ b/src/database/rules.ts @@ -61,19 +61,21 @@ export class Rules { hasPermission( path: string[], session: Session - ): { read: boolean; write: boolean } { + ): { read: boolean; write: boolean; path: string[] } { if (session.root) return { read: true, - write: true + write: true, + path: path }; let read = this.rules[".read"] || false; let write = this.rules[".write"] || false; let rules = this.rules; - for (let segment of path) { - if (segment.startsWith("$") || segment.startsWith(".")) { + for (let idx in path) { + let segment = path[idx]; + if (segment.startsWith(".")) { read = false; write = false; Logging.log("Invalid query path (started with '$' or '.'):", path); @@ -85,6 +87,10 @@ export class Rules { .find(e => { switch (e) { case "$uid": + if (segment === "$uid") { + path[idx] = session.uid; + return true; + } if (segment === session.uid) return true; break; } @@ -108,7 +114,8 @@ export class Rules { return { read: read as boolean, - write: write as boolean + write: write as boolean, + path }; } diff --git a/src/web/helper/form.ts b/src/web/helper/form.ts index 7e5271c..007542d 100644 --- a/src/web/helper/form.ts +++ b/src/web/helper/form.ts @@ -5,16 +5,26 @@ interface IFormConfigField { type: "text" | "number" | "boolean" | "textarea"; label: string; value?: string; + disabled?: boolean; } -type IFormConfig = { [name: string]: IFormConfigField } +type IFormConfig = { [name: string]: IFormConfigField }; -export default function getForm(url: string, title: string, fieldConfig: IFormConfig): (ctx: Context) => void { - let fields = Object.keys(fieldConfig).map(name => ({ name, ...fieldConfig[name] })) +export default function getForm( + url: string, + title: string, + fieldConfig: IFormConfig +): (ctx: Context) => void { + let fields = Object.keys(fieldConfig).map(name => ({ + name, + ...fieldConfig[name], + disabled: fieldConfig.disabled ? "disabled" : "" + })); - return ctx => ctx.body = getTemplate("forms")({ - url, - title, - fields - }); -} \ No newline at end of file + return ctx => + (ctx.body = getTemplate("forms")({ + url, + title, + fields + })); +} diff --git a/src/web/v1/admin.ts b/src/web/v1/admin.ts index 3b4da82..1ea7a83 100644 --- a/src/web/v1/admin.ts +++ b/src/web/v1/admin.ts @@ -2,7 +2,11 @@ import * as Router from "koa-router"; import Settings from "../../settings"; import getForm from "../helper/form"; import getTable from "../helper/table"; -import { BadRequestError, NoPermissionError } from "../helper/errors"; +import { + BadRequestError, + NoPermissionError, + NotFoundError +} from "../helper/errors"; import { DatabaseManager } from "../../database/database"; import { MP } from "../../database/query"; import config from "../../config"; @@ -13,10 +17,9 @@ const AdminRoute = new Router(); AdminRoute.use(async (ctx, next) => { const { key } = ctx.query; - if (key !== config.admin) - throw new NoPermissionError("No permission!"); + if (key !== config.admin) throw new NoPermissionError("No permission!"); return next(); -}) +}); AdminRoute.get("/", async ctx => { //TODO: Main Interface @@ -33,26 +36,24 @@ AdminRoute.get("/settings", async ctx => { let res = [["key", "value"]]; stream.on("data", ({ key, value }) => { res.push([key, value]); - }) + }); stream.on("error", no); - stream.on("end", () => yes(res)) - }) + stream.on("end", () => yes(res)); + }); if (ctx.query.view) { return getTable("Settings", res, ctx); } else { ctx.body = res; } -}) +}); AdminRoute.get("/data", async ctx => { const { database } = ctx.query; let db = DatabaseManager.getDatabase(database); - if (!db) - throw new BadRequestError("Database not found"); + if (!db) throw new BadRequestError("Database not found"); let res = await new Promise((yes, no) => { - const stream = db.data.createReadStream({ keys: true, values: true, @@ -61,73 +62,71 @@ AdminRoute.get("/data", async ctx => { limit: 1000 }); let res = [["key", "value"]]; - stream.on("data", ({ key, value }: { key: string, value: Buffer }) => { - res.push([key, key.split("/").length > 2 ? value.toString() : JSON.stringify(MP.decode(value))]); - }) + stream.on("data", ({ key, value }: { key: string; value: Buffer }) => { + res.push([ + key, + key.split("/").length > 2 + ? value.toString() + : JSON.stringify(MP.decode(value)) + ]); + }); stream.on("error", no); - stream.on("end", () => yes(res)) - }) + stream.on("end", () => yes(res)); + }); if (ctx.query.view) { return getTable("Data from " + database, res, ctx); } else { ctx.body = res; } -}) +}); -AdminRoute - .get("/database", ctx => { - const isFull = ctx.query.full === "true" || ctx.query.full === "1"; - let res; - if (isFull) { - //TODO: Better than JSON.parse / JSON.stringify - res = Array.from(DatabaseManager.databases.entries()).map(([name, config]) => ({ name, ...(JSON.parse(JSON.stringify(config))) })); - } else { - res = Array.from(DatabaseManager.databases.keys()); - } +AdminRoute.get("/database", ctx => { + const isFull = ctx.query.full === "true" || ctx.query.full === "1"; + let res; + if (isFull) { + //TODO: Better than JSON.parse / JSON.stringify + res = Array.from(DatabaseManager.databases.entries()).map( + ([name, config]) => ({ + name, + ...JSON.parse(JSON.stringify(config)) + }) + ); + } else { + res = Array.from(DatabaseManager.databases.keys()); + } - if (ctx.query.view) { - return getTable("Databases" + (isFull ? "" : " small"), res, ctx); - } else { - ctx.body = res; - } - }) - .post("/database", async ctx => { - const { name, rules, publickey, accesskey, rootkey } = ctx.request.body; + if (ctx.query.view) { + return getTable("Databases" + (isFull ? "" : " small"), res, ctx); + } else { + ctx.body = res; + } +}).post("/database", async ctx => { + const { name, rules, publickey, accesskey, rootkey } = ctx.request.body; - if (!name) - throw new BadRequestError("Name must be set!"); + if (!name) throw new BadRequestError("Name must be set!"); - let db = DatabaseManager.getDatabase(name); - if (!db) - db = await DatabaseManager.addDatabase(name); + let db = DatabaseManager.getDatabase(name); + if (!db) db = await DatabaseManager.addDatabase(name); - if (publickey) - await db.setPublicKey(publickey); + if (publickey) await db.setPublicKey(publickey); - if (rules) - await db.setRules(rules); + if (rules) await db.setRules(rules); - if (accesskey) - await db.setAccessKey(accesskey); + if (accesskey) await db.setAccessKey(accesskey); + if (rootkey) await db.setRootKey(rootkey); - if (rootkey) - await db.setRootKey(rootkey); - - - ctx.body = "Success"; - }) + ctx.body = "Success"; +}); AdminRoute.get("/collections", async ctx => { const { database } = ctx.query; let db = DatabaseManager.getDatabase(database); - if (!db) - throw new BadRequestError("Database not found"); + if (!db) throw new BadRequestError("Database not found"); let res = await new Promise((yes, no) => { - const stream = db.collections.createKeyStream({ keyAsBuffer: false, limit: 1000 @@ -135,24 +134,23 @@ AdminRoute.get("/collections", async ctx => { let res = []; stream.on("data", (key: string) => { res.push(key); - }) + }); stream.on("error", no); - stream.on("end", () => yes(res)) - }) + stream.on("end", () => yes(res)); + }); if (ctx.query.view) { return getTable("Databases", res, ctx); } else { ctx.body = res; } -}) +}); AdminRoute.get("/collections/cleanup", async ctx => { const { database } = ctx.query; let db = DatabaseManager.getDatabase(database); - if (!db) - throw new BadRequestError("Database not found"); + if (!db) throw new BadRequestError("Database not found"); let deleted = await db.runCleanup(); if (ctx.query.view) { @@ -160,14 +158,55 @@ AdminRoute.get("/collections/cleanup", async ctx => { } else { ctx.body = deleted; } -}) +}); -AdminRoute.get("/database/new", getForm("/v1/admin/database", "New/Change Database", { - name: { label: "Name", type: "text", }, - accesskey: { label: "Access Key", type: "text" }, - rootkey: { label: "Root access key", type: "text" }, - rules: { label: "Rules", type: "textarea", value: `{\n ".write": true, \n ".read": true \n}` }, - publickey: { label: "Public Key", type: "textarea" } -})) +AdminRoute.get( + "/database/new", + getForm("/v1/admin/database", "New Database", { + name: { label: "Name", type: "text" }, + accesskey: { label: "Access Key", type: "text" }, + rootkey: { label: "Root access key", type: "text" }, + rules: { + label: "Rules", + type: "textarea", + value: `{\n ".write": true, \n ".read": true \n}` + }, + publickey: { label: "Public Key", type: "textarea" } + }) +); -export default AdminRoute; \ No newline at end of file +AdminRoute.get("/database/update", async ctx => { + const { database } = ctx.query; + let db = DatabaseManager.getDatabase(database); + if (!db) throw new NotFoundError("Database not found!"); + getForm("/v1/admin/database", "Change Database", { + name: { + label: "Name", + type: "text", + value: db.name, + disabled: true + }, + accesskey: { + label: "Access Key", + type: "text", + value: db.accesskey + }, + rootkey: { + label: "Root access key", + type: "text", + value: db.rootkey + }, + rules: { + label: "Rules", + type: "textarea", + value: db.rules.toJSON() + }, + publickey: { + label: "Public Key", + type: "textarea", + value: db.publickey + } + })(ctx); +}); + +export default AdminRoute; diff --git a/views/admin.html b/views/admin.html index 9e5893b..246f876 100644 --- a/views/admin.html +++ b/views/admin.html @@ -1,112 +1,118 @@ + + + + + Admin Interface + + - - - - - Admin Interface - - + - + - - - -
-
-

Navigation:

-
    -
  • Settings
  • -
  • Databases
  • -
  • New Database
  • -
- Databases: -
+ #content { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; + } + + + +
+
+

Navigation:

+
    +
  • Settings
  • +
  • Databases
  • +
  • New Database
  • +
+ Databases: +
+
+
+ +
-
- -
-
- + - - - \ No newline at end of file + reloadDBs(); + setInterval(reloadDBs, 5000); + + + diff --git a/views/forms.hbs b/views/forms.hbs index 6f1a6a8..1ced95b 100644 --- a/views/forms.hbs +++ b/views/forms.hbs @@ -32,19 +32,19 @@
{{#ifCond type "===" "text"}} - + {{/ifCond}} {{#ifCond type "===" "number"}} - + {{/ifCond}} {{#ifCond type "===" "boolean"}} - + {{/ifCond}} {{#ifCond type "===" "textarea"}} - + {{/ifCond}}
{{/each}} @@ -83,4 +83,4 @@ - \ No newline at end of file +