diff options
Diffstat (limited to 'src/server')
-rw-r--r-- | src/server/ApiManagers/DeleteManager.ts | 85 | ||||
-rw-r--r-- | src/server/ApiManagers/GeneralGoogleManager.ts | 19 | ||||
-rw-r--r-- | src/server/ApiManagers/GooglePhotosManager.ts | 2 | ||||
-rw-r--r-- | src/server/ApiManagers/SessionManager.ts | 2 | ||||
-rw-r--r-- | src/server/ApiManagers/UploadManager.ts | 2 | ||||
-rw-r--r-- | src/server/ApiManagers/UserManager.ts | 2 | ||||
-rw-r--r-- | src/server/DashSession/DashSessionAgent.ts | 2 | ||||
-rw-r--r-- | src/server/GarbageCollector.ts | 6 | ||||
-rw-r--r-- | src/server/IDatabase.ts | 4 | ||||
-rw-r--r-- | src/server/MemoryDatabase.ts | 23 | ||||
-rw-r--r-- | src/server/Websocket/Websocket.ts | 37 | ||||
-rw-r--r-- | src/server/apis/google/GoogleApiServerUtils.ts | 47 | ||||
-rw-r--r-- | src/server/authentication/models/current_user_utils.ts | 36 | ||||
-rw-r--r-- | src/server/authentication/models/user_model.ts | 4 | ||||
-rw-r--r-- | src/server/database.ts | 118 | ||||
-rw-r--r-- | src/server/remapUrl.ts | 4 |
16 files changed, 173 insertions, 220 deletions
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts index 9e70af2eb..bd80d6500 100644 --- a/src/server/ApiManagers/DeleteManager.ts +++ b/src/server/ApiManagers/DeleteManager.ts @@ -1,12 +1,12 @@ import ApiManager, { Registration } from "./ApiManager"; -import { Method, _permission_denied, PublicHandler } from "../RouteManager"; +import { Method, _permission_denied } from "../RouteManager"; import { WebSocket } from "../Websocket/Websocket"; import { Database } from "../database"; import rimraf = require("rimraf"); -import { pathToDirectory, Directory } from "./UploadManager"; import { filesDirectory } from ".."; import { DashUploadUtils } from "../DashUploadUtils"; import { mkdirSync } from "fs"; +import RouteSubscriber from "../RouteSubscriber"; export default class DeleteManager extends ApiManager { @@ -14,68 +14,39 @@ export default class DeleteManager extends ApiManager { register({ method: Method.GET, - subscription: "/delete", - secureHandler: async ({ res, isRelease }) => { + subscription: new RouteSubscriber("delete").add("target?"), + secureHandler: async ({ req, res, isRelease }) => { if (isRelease) { - return _permission_denied(res, deletionPermissionError); + return _permission_denied(res, "Cannot perform a delete operation outside of the development environment!"); } - await WebSocket.deleteFields(); - res.redirect("/home"); - } - }); - register({ - method: Method.GET, - subscription: "/deleteAll", - secureHandler: async ({ res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, deletionPermissionError); + const { target } = req.params; + const { doDelete } = WebSocket; + + if (!target) { + await doDelete(); + } else { + let all = false; + switch (target) { + case "all": + all = true; + case "database": + await doDelete(false); + if (!all) break; + case "files": + rimraf.sync(filesDirectory); + mkdirSync(filesDirectory); + await DashUploadUtils.buildFileDirectories(); + break; + default: + await Database.Instance.dropSchema(target); + } } - await WebSocket.deleteAll(); - res.redirect("/home"); - } - }); - register({ - method: Method.GET, - subscription: "/deleteAssets", - secureHandler: async ({ res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, deletionPermissionError); - } - rimraf.sync(filesDirectory); - mkdirSync(filesDirectory); - await DashUploadUtils.buildFileDirectories(); - res.redirect("/delete"); - } - }); - - register({ - method: Method.GET, - subscription: "/deleteWithAux", - secureHandler: async ({ res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, deletionPermissionError); - } - await Database.Auxiliary.DeleteAll(); - res.redirect("/delete"); - } - }); - - register({ - method: Method.GET, - subscription: "/deleteWithGoogleCredentials", - secureHandler: async ({ res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, deletionPermissionError); - } - await Database.Auxiliary.GoogleAuthenticationToken.DeleteAll(); - res.redirect("/delete"); + res.redirect("/home"); } }); } -} - -const deletionPermissionError = "Cannot perform a delete operation outside of the development environment!"; +}
\ No newline at end of file diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts index a5240edbc..17968cc7d 100644 --- a/src/server/ApiManagers/GeneralGoogleManager.ts +++ b/src/server/ApiManagers/GeneralGoogleManager.ts @@ -1,10 +1,8 @@ import ApiManager, { Registration } from "./ApiManager"; import { Method, _permission_denied } from "../RouteManager"; import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils"; -import { Database } from "../database"; import RouteSubscriber from "../RouteSubscriber"; - -const deletionPermissionError = "Cannot perform specialized delete outside of the development environment!"; +import { Database } from "../database"; const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerUtils.ApiRouter>([ ["create", (api, params) => api.create(params)], @@ -20,11 +18,11 @@ export default class GeneralGoogleManager extends ApiManager { method: Method.GET, subscription: "/readGoogleAccessToken", secureHandler: async ({ user, res }) => { - const token = await GoogleApiServerUtils.retrieveAccessToken(user.id); - if (!token) { + const { credentials } = (await GoogleApiServerUtils.retrieveCredentials(user.id)); + if (!credentials?.access_token) { return res.send(GoogleApiServerUtils.generateAuthenticationUrl()); } - return res.send(token); + return res.send(credentials); } }); @@ -37,6 +35,15 @@ export default class GeneralGoogleManager extends ApiManager { }); register({ + method: Method.GET, + subscription: "/revokeGoogleAccessToken", + secureHandler: async ({ user, res }) => { + await Database.Auxiliary.GoogleAuthenticationToken.Revoke(user.id); + res.send(); + } + }); + + register({ method: Method.POST, subscription: new RouteSubscriber("googleDocs").add("sector", "action"), secureHandler: async ({ req, res, user }) => { diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index 88219423d..11841a603 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -56,7 +56,7 @@ export default class GooglePhotosManager extends ApiManager { const { media } = req.body; // first we need to ensure that we know the google account to which these photos will be uploaded - const token = await GoogleApiServerUtils.retrieveAccessToken(user.id); + const token = (await GoogleApiServerUtils.retrieveCredentials(user.id))?.credentials?.access_token; if (!token) { return _error(res, authenticationError); } diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts index bcaa6598f..fa2f6002a 100644 --- a/src/server/ApiManagers/SessionManager.ts +++ b/src/server/ApiManagers/SessionManager.ts @@ -55,7 +55,7 @@ export default class SessionManager extends ApiManager { register({ method: Method.GET, - subscription: this.secureSubscriber("delete"), + subscription: this.secureSubscriber("deleteSession"), secureHandler: this.authorizedAction(async ({ res }) => { const { error } = await sessionAgent.serverWorker.emit("delete"); res.send(error ? error.message : "Your request was successful: the server successfully deleted the database. Return to /home."); diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 98f029c7d..b185d3b55 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -171,7 +171,7 @@ export default class UploadManager extends ApiManager { await Promise.all(docs.map((doc: any) => new Promise(res => Database.Instance.replace(doc.id, doc, (err, r) => { err && console.log(err); res(); - }, true, "newDocuments")))); + }, true)))); } catch (e) { console.log(e); } unlink(path_2, () => { }); } diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts index d9d346cc1..68b3107ae 100644 --- a/src/server/ApiManagers/UserManager.ts +++ b/src/server/ApiManagers/UserManager.ts @@ -89,8 +89,6 @@ export default class UserManager extends ApiManager { } }); - - register({ method: Method.GET, subscription: "/activity", diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts index 5cbba13de..ef9b88541 100644 --- a/src/server/DashSession/DashSessionAgent.ts +++ b/src/server/DashSession/DashSessionAgent.ts @@ -37,7 +37,7 @@ export class DashSessionAgent extends AppliedSessionAgent { monitor.addReplCommand("debug", [/\S+\@\S+/], async ([to]) => this.dispatchZippedDebugBackup(to)); monitor.on("backup", this.backup); monitor.on("debug", async ({ to }) => this.dispatchZippedDebugBackup(to)); - monitor.on("delete", WebSocket.deleteFields); + monitor.on("delete", WebSocket.doDelete); monitor.coreHooks.onCrashDetected(this.dispatchCrashReport); return sessionKey; } diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts index 5729c3ee5..24745cbb4 100644 --- a/src/server/GarbageCollector.ts +++ b/src/server/GarbageCollector.ts @@ -76,7 +76,7 @@ async function GarbageCollect(full: boolean = true) { if (!fetchIds.length) { continue; } - const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res, "newDocuments")); + const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res)); for (const doc of docs) { const id = doc.id; if (doc === undefined) { @@ -116,10 +116,10 @@ async function GarbageCollect(full: boolean = true) { const count = Math.min(toDelete.length, 5000); const toDeleteDocs = toDelete.slice(i, i + count); i += count; - const result = await Database.Instance.delete({ _id: { $in: toDeleteDocs } }, "newDocuments"); + const result = await Database.Instance.delete({ _id: { $in: toDeleteDocs } }); deleted += result.deletedCount || 0; } - // const result = await Database.Instance.delete({ _id: { $in: toDelete } }, "newDocuments"); + // const result = await Database.Instance.delete({ _id: { $in: toDelete } }); console.log(`${deleted} documents deleted`); await Search.deleteDocuments(toDelete); diff --git a/src/server/IDatabase.ts b/src/server/IDatabase.ts index 6a63df485..dd4968579 100644 --- a/src/server/IDatabase.ts +++ b/src/server/IDatabase.ts @@ -2,7 +2,6 @@ import * as mongodb from 'mongodb'; import { Transferable } from './Message'; export const DocumentsCollection = 'documents'; -export const NewDocumentsCollection = 'newDocuments'; export interface IDatabase { update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert?: boolean, collectionName?: string): Promise<void>; updateMany(query: any, update: any, collectionName?: string): Promise<mongodb.WriteOpResult>; @@ -12,12 +11,13 @@ export interface IDatabase { delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; - deleteAll(collectionName?: string, persist?: boolean): Promise<any>; + dropSchema(...schemaNames: string[]): Promise<any>; insert(value: any, collectionName?: string): Promise<void>; getDocument(id: string, fn: (result?: Transferable) => void, collectionName?: string): void; getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName?: string): void; + getCollectionNames(): Promise<string[]>; visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName?: string): Promise<void>; query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName?: string): Promise<mongodb.Cursor>; diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts index 543f96e7f..1f1d702d9 100644 --- a/src/server/MemoryDatabase.ts +++ b/src/server/MemoryDatabase.ts @@ -1,4 +1,4 @@ -import { IDatabase, DocumentsCollection, NewDocumentsCollection } from './IDatabase'; +import { IDatabase, DocumentsCollection } from './IDatabase'; import { Transferable } from './Message'; import * as mongodb from 'mongodb'; @@ -15,6 +15,10 @@ export class MemoryDatabase implements IDatabase { } } + public getCollectionNames() { + return Promise.resolve(Object.keys(this.db)); + } + public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> { const collection = this.getCollection(collectionName); const set = "$set"; @@ -41,7 +45,7 @@ export class MemoryDatabase implements IDatabase { return Promise.resolve(undefined); } - public updateMany(query: any, update: any, collectionName = NewDocumentsCollection): Promise<mongodb.WriteOpResult> { + public updateMany(query: any, update: any, collectionName = DocumentsCollection): Promise<mongodb.WriteOpResult> { throw new Error("Can't updateMany a MemoryDatabase"); } @@ -58,8 +62,15 @@ export class MemoryDatabase implements IDatabase { return Promise.resolve({} as any); } - public deleteAll(collectionName = DocumentsCollection, _persist = true): Promise<any> { - delete this.db[collectionName]; + public async dropSchema(...schemaNames: string[]): Promise<any> { + const existing = await this.getCollectionNames(); + let valid: string[]; + if (schemaNames.length) { + valid = schemaNames.filter(collection => existing.includes(collection)); + } else { + valid = existing; + } + valid.forEach(schemaName => delete this.db[schemaName]); return Promise.resolve(); } @@ -69,14 +80,14 @@ export class MemoryDatabase implements IDatabase { return Promise.resolve(); } - public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = NewDocumentsCollection): void { + public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection): void { fn(this.getCollection(collectionName)[id]); } public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection): void { fn(ids.map(id => this.getCollection(collectionName)[id])); } - public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = NewDocumentsCollection): Promise<void> { + public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = DocumentsCollection): Promise<void> { const visited = new Set<string>(); while (ids.length) { const count = Math.min(ids.length, 1000); diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index be895c4bc..844535056 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -12,6 +12,7 @@ import { timeMap } from "../ApiManagers/UserManager"; import { green } from "colors"; import { networkInterfaces } from "os"; import executeImport from "../../scraping/buxton/final/BuxtonImporter"; +import { DocumentsCollection } from "../IDatabase"; export namespace WebSocket { @@ -96,7 +97,7 @@ export namespace WebSocket { Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField); Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields); if (isRelease) { - Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields); + Utils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false)); } Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField); @@ -163,24 +164,10 @@ export namespace WebSocket { } } - export async function deleteFields() { - await Database.Instance.deleteAll(); - if (process.env.DISABLE_SEARCH !== "true") { - await Search.clear(); - } - await Database.Instance.deleteAll('newDocuments'); - } - - // export async function deleteUserDocuments() { - // await Database.Instance.deleteAll(); - // await Database.Instance.deleteAll('newDocuments'); - // } - - export async function deleteAll() { - await Database.Instance.deleteAll(); - await Database.Instance.deleteAll('newDocuments'); - await Database.Instance.deleteAll('sessions'); - await Database.Instance.deleteAll('users'); + export async function doDelete(onlyFields = true) { + const target: string[] = []; + onlyFields && target.push(DocumentsCollection); + await Database.Instance.dropSchema(...target); if (process.env.DISABLE_SEARCH !== "true") { await Search.clear(); } @@ -210,11 +197,11 @@ export namespace WebSocket { } function GetRefField([id, callback]: [string, (result?: Transferable) => void]) { - Database.Instance.getDocument(id, callback, "newDocuments"); + Database.Instance.getDocument(id, callback); } function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) { - Database.Instance.getDocuments(ids, callback, "newDocuments"); + Database.Instance.getDocuments(ids, callback); } const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = { @@ -271,7 +258,7 @@ export namespace WebSocket { function UpdateField(socket: Socket, diff: Diff) { Database.Instance.update(diff.id, diff.diff, - () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments"); + () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false); const docfield = diff.diff.$set || diff.diff.$unset; if (!docfield) { return; @@ -296,7 +283,7 @@ export namespace WebSocket { } function DeleteField(socket: Socket, id: string) { - Database.Instance.delete({ _id: id }, "newDocuments").then(() => { + Database.Instance.delete({ _id: id }).then(() => { socket.broadcast.emit(MessageStore.DeleteField.Message, id); }); @@ -304,14 +291,14 @@ export namespace WebSocket { } function DeleteFields(socket: Socket, ids: string[]) { - Database.Instance.delete({ _id: { $in: ids } }, "newDocuments").then(() => { + Database.Instance.delete({ _id: { $in: ids } }).then(() => { socket.broadcast.emit(MessageStore.DeleteFields.Message, ids); }); Search.deleteDocuments(ids); } function CreateField(newValue: any) { - Database.Instance.insert(newValue, "newDocuments"); + Database.Instance.insert(newValue); } } diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 0f75833ee..48a8da89f 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -149,26 +149,6 @@ export namespace GoogleApiServerUtils { } /** - * Returns the lengthy string or access token that can be passed into - * the headers of an API request or into the constructor of the Photos - * client API wrapper. - * @param userId the Dash user id of the user requesting his/her associated - * access_token - * @returns the current access_token associated with the requesting - * Dash user. The access_token is valid for only an hour, and - * is then refreshed. - */ - export async function retrieveAccessToken(userId: string): Promise<string> { - return new Promise(async resolve => { - const { credentials } = await retrieveCredentials(userId); - if (!credentials) { - return resolve(); - } - resolve(credentials.access_token!); - }); - } - - /** * Manipulates a mapping such that, in the limit, each Dash user has * an associated authenticated OAuth2 client at their disposal. This * function ensures that the client's credentials always remain up to date @@ -217,18 +197,6 @@ export namespace GoogleApiServerUtils { } /** - * This is what we return to the server in processNewUser(), after the - * worker OAuth2Client has used the user-pasted authentication code - * to retrieve an access token and an info token. The avatar is the - * URL to the Google-hosted mono-color, single white letter profile 'image'. - */ - export interface GoogleAuthenticationResult { - access_token: string; - avatar: string; - name: string; - } - - /** * This method receives the authentication code that the * user pasted into the overlay in the client side and uses the worker * and the authentication code to fetch the full set of credentials that @@ -245,7 +213,7 @@ export namespace GoogleApiServerUtils { * and display basic user information in the overlay on successful authentication. * This can be expanded as needed by adding properties to the interface GoogleAuthenticationResult. */ - export async function processNewUser(userId: string, authenticationCode: string): Promise<GoogleAuthenticationResult> { + export async function processNewUser(userId: string, authenticationCode: string): Promise<EnrichedCredentials> { const credentials = await new Promise<Credentials>((resolve, reject) => { worker.getToken(authenticationCode, async (err, credentials) => { if (err || !credentials) { @@ -257,12 +225,7 @@ export namespace GoogleApiServerUtils { }); const enriched = injectUserInfo(credentials); await Database.Auxiliary.GoogleAuthenticationToken.Write(userId, enriched); - const { given_name, picture } = enriched.userInfo; - return { - access_token: enriched.access_token!, - avatar: picture, - name: given_name - }; + return enriched; } /** @@ -316,15 +279,15 @@ export namespace GoogleApiServerUtils { * @returns the credentials, or undefined if the user has no stored associated credentials, * and a flag indicating whether or not they were refreshed during retrieval */ - async function retrieveCredentials(userId: string): Promise<{ credentials: Opt<Credentials>, refreshed: boolean }> { - let credentials: Opt<Credentials> = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId); + export async function retrieveCredentials(userId: string): Promise<{ credentials: Opt<EnrichedCredentials>, refreshed: boolean }> { + let credentials = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId); let refreshed = false; if (!credentials) { return { credentials: undefined, refreshed }; } // check for token expiry if (credentials.expiry_date! <= new Date().getTime()) { - credentials = await refreshAccessToken(credentials, userId); + credentials = { ...credentials, ...(await refreshAccessToken(credentials, userId)) }; refreshed = true; } return { credentials, refreshed }; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index d3e085b21..e7b448358 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -306,7 +306,7 @@ export class CurrentUserUtils { { _width: 250, _height: 250, title: "container" }); } if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600 }) + doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600 }); } return [ { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc }, @@ -328,7 +328,8 @@ export class CurrentUserUtils { // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc }, // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc }, { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, - { title: "Drag a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, + { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, + { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, ]; } @@ -344,21 +345,22 @@ export class CurrentUserUtils { alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title)); } } - const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)). - map(data => Docs.Create.FontIconDocument({ - _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, - icon: data.icon, - title: data.title, - label: data.label, - ignoreClick: data.ignoreClick, - dropAction: "copy", - onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, - onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, - ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, - activePen: data.activePen, - backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), - dragFactory: data.dragFactory, - })); + const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)); + const creatorBtns = buttons.map(({ title, label, icon, ignoreClick, drag, click, ischecked, activePen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({ + _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, + icon, + title, + label, + ignoreClick, + dropAction: "copy", + onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, + onClick: click ? ScriptField.MakeScript(click) : undefined, + ischecked: ischecked ? ComputedField.MakeFunction(ischecked) : undefined, + activePen, + backgroundColor, + removeDropProperties: new List<string>(["dropAction"]), + dragFactory, + })); if (dragCreatorSet === undefined) { doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, { diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts index 78e39dbc1..a0b688328 100644 --- a/src/server/authentication/models/user_model.ts +++ b/src/server/authentication/models/user_model.ts @@ -74,9 +74,9 @@ userSchema.pre("save", function save(next) { const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) { // Choose one of the following bodies for authentication logic. - // secure + // secure (expected, default) bcrypt.compare(candidatePassword, this.password, cb); - // bypass password + // bypass password (debugging) // cb(undefined, true); }; diff --git a/src/server/database.ts b/src/server/database.ts index ad285765b..9ba461b65 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -4,7 +4,7 @@ import { Opt } from '../new_fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { Credentials } from 'google-auth-library'; import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; -import { IDatabase } from './IDatabase'; +import { IDatabase, DocumentsCollection } from './IDatabase'; import { MemoryDatabase } from './MemoryDatabase'; import * as mongoose from 'mongoose'; import { Upload } from './SharedMediaTypes'; @@ -14,7 +14,7 @@ export namespace Database { export let disconnect: Function; const schema = 'Dash'; const port = 27017; - export const url = `mongodb://localhost:${port}/`; + export const url = `mongodb://localhost:${port}/${schema}`; enum ConnectionStates { disconnected = 0, @@ -47,28 +47,29 @@ export namespace Database { } export class Database implements IDatabase { - public static DocumentsCollection = 'documents'; private MongoClient = mongodb.MongoClient; private currentWrites: { [id: string]: Promise<void> } = {}; private db?: mongodb.Db; private onConnect: (() => void)[] = []; - doConnect() { + async doConnect() { console.error(`\nConnecting to Mongo with URL : ${url}\n`); - this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000, useUnifiedTopology: true }, (_err, client) => { - console.error("mongo connect response\n"); - if (!client) { - console.error("\nMongo connect failed with the error:\n"); - console.log(_err); - process.exit(0); - } - this.db = client.db(); - this.onConnect.forEach(fn => fn()); + return new Promise<void>(resolve => { + this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000, useUnifiedTopology: true }, (_err, client) => { + console.error("mongo connect response\n"); + if (!client) { + console.error("\nMongo connect failed with the error:\n"); + console.log(_err); + process.exit(0); + } + this.db = client.db(); + this.onConnect.forEach(fn => fn()); + resolve(); + }); }); } - public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) { - + public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = DocumentsCollection) { if (this.db) { const collection = this.db.collection(collectionName); const prom = this.currentWrites[id]; @@ -93,7 +94,7 @@ export namespace Database { } } - public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) { + public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = DocumentsCollection) { if (this.db) { const collection = this.db.collection(collectionName); const prom = this.currentWrites[id]; @@ -117,9 +118,25 @@ export namespace Database { } } + public async getCollectionNames() { + const cursor = this.db?.listCollections(); + const collectionNames: string[] = []; + if (cursor) { + while (await cursor.hasNext()) { + const collection: any = await cursor.next(); + collection && collectionNames.push(collection.name); + } + } + return collectionNames; + } + + public async clear() { + return Promise.all((await this.getCollectionNames()).map(collection => this.dropSchema(collection))); + } + public delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; public delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; - public delete(id: any, collectionName = Database.DocumentsCollection) { + public delete(id: any, collectionName = DocumentsCollection) { if (typeof id === "string") { id = { _id: id }; } @@ -131,25 +148,26 @@ export namespace Database { } } - public async deleteAll(collectionName = Database.DocumentsCollection, persist = true): Promise<any> { - return new Promise(resolve => { - const executor = async (database: mongodb.Db) => { - if (persist) { - await database.collection(collectionName).deleteMany({}); - } else { - await database.dropCollection(collectionName); - } - resolve(); - }; - if (this.db) { - executor(this.db); + public async dropSchema(...targetSchemas: string[]): Promise<any> { + const executor = async (database: mongodb.Db) => { + const existing = await Instance.getCollectionNames(); + let valid: string[]; + if (targetSchemas.length) { + valid = targetSchemas.filter(collection => existing.includes(collection)); } else { - this.onConnect.push(() => this.db && executor(this.db)); + valid = existing; } - }); + const pending = Promise.all(valid.map(schemaName => database.dropCollection(schemaName))); + return (await pending).every(dropOutcome => dropOutcome); + }; + if (this.db) { + return executor(this.db); + } else { + this.onConnect.push(() => this.db && executor(this.db)); + } } - public async insert(value: any, collectionName = Database.DocumentsCollection) { + public async insert(value: any, collectionName = DocumentsCollection) { if (this.db) { if ("id" in value) { value._id = value.id; @@ -177,7 +195,7 @@ export namespace Database { } } - public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = "newDocuments") { + public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection) { if (this.db) { this.db.collection(collectionName).findOne({ _id: id }, (err, result) => { if (result) { @@ -193,7 +211,7 @@ export namespace Database { } } - public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = Database.DocumentsCollection) { + public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection) { if (this.db) { this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => { if (err) { @@ -211,7 +229,7 @@ export namespace Database { } } - public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = "newDocuments"): Promise<void> { + public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = DocumentsCollection): Promise<void> { if (this.db) { const visited = new Set<string>(); while (ids.length) { @@ -238,7 +256,7 @@ export namespace Database { } } - public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = "newDocuments"): Promise<mongodb.Cursor> { + public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = DocumentsCollection): Promise<mongodb.Cursor> { if (this.db) { let cursor = this.db.collection(collectionName).find(query); if (projection) { @@ -252,7 +270,7 @@ export namespace Database { } } - public updateMany(query: any, update: any, collectionName = "newDocuments") { + public updateMany(query: any, update: any, collectionName = DocumentsCollection) { if (this.db) { const db = this.db; return new Promise<mongodb.WriteOpResult>(res => db.collection(collectionName).update(query, update, (_, result) => res(result))); @@ -282,7 +300,8 @@ export namespace Database { export namespace Auxiliary { export enum AuxiliaryCollections { - GooglePhotosUploadHistory = "uploadedFromGooglePhotos" + GooglePhotosUploadHistory = "uploadedFromGooglePhotos", + GoogleAuthentication = "googleAuthentication" } const SanitizedCappedQuery = async (query: { [key: string]: any }, collection: string, cap: number, removeId = true) => { @@ -306,27 +325,30 @@ export namespace Database { export namespace GoogleAuthenticationToken { - const GoogleAuthentication = "googleAuthentication"; - - export type StoredCredentials = Credentials & { _id: string }; + type StoredCredentials = GoogleApiServerUtils.EnrichedCredentials & { _id: string }; export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => { - return SanitizedSingletonQuery<StoredCredentials>({ userId }, GoogleAuthentication, removeId); + return SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAuthentication, removeId); }; export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => { - return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, GoogleAuthentication); + return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAuthentication); }; export const Update = async (userId: string, access_token: string, expiry_date: number) => { const entry = await Fetch(userId, false); if (entry) { const parameters = { $set: { access_token, expiry_date } }; - return Instance.update(entry._id, parameters, emptyFunction, true, GoogleAuthentication); + return Instance.update(entry._id, parameters, emptyFunction, true, AuxiliaryCollections.GoogleAuthentication); } }; - export const DeleteAll = () => Instance.deleteAll(GoogleAuthentication, false); + export const Revoke = async (userId: string) => { + const entry = await Fetch(userId, false); + if (entry) { + Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAuthentication); + } + }; } @@ -338,12 +360,6 @@ export namespace Database { return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory); }; - export const DeleteAll = async (persist = false) => { - const collectionNames = Object.values(AuxiliaryCollections); - const pendingDeletions = collectionNames.map(name => Instance.deleteAll(name, persist)); - return Promise.all(pendingDeletions); - }; - } } diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts index 45d2fdd33..91a3cb6bf 100644 --- a/src/server/remapUrl.ts +++ b/src/server/remapUrl.ts @@ -1,6 +1,4 @@ import { Database } from "./database"; -import { Search } from "./Search"; -import * as path from 'path'; //npx ts-node src/server/remapUrl.ts @@ -50,7 +48,7 @@ async function update() { return new Promise(res => Database.Instance.update(doc[0], doc[1], () => { console.log("wrote " + JSON.stringify(doc[1])); res(); - }, false, "newDocuments")); + }, false)); })); console.log("Done"); // await Promise.all(updates.map(update => { |