aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/SettingsManager.scss120
-rw-r--r--src/client/util/SettingsManager.tsx124
-rw-r--r--src/client/views/MainView.scss16
-rw-r--r--src/client/views/MainView.tsx8
-rw-r--r--src/server/ApiManagers/DeleteManager.ts17
-rw-r--r--src/server/ApiManagers/UserManager.ts55
-rw-r--r--src/server/RouteManager.ts33
7 files changed, 356 insertions, 17 deletions
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
new file mode 100644
index 000000000..5839fa748
--- /dev/null
+++ b/src/client/util/SettingsManager.scss
@@ -0,0 +1,120 @@
+@import "../views/globalCssVariables";
+
+.dialogue-box {
+ background-color: whitesmoke !important;
+ color: grey;
+
+ button {
+ background: $lighter-alt-accent;
+ outline: none;
+ border-radius: 5px;
+ border: 0px;
+ color: #fcfbf7;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ padding: 10px;
+ transition: transform 0.2s;
+ margin: 2px;
+ }
+}
+
+.settings-interface {
+ display: flex;
+ flex-direction: column;
+
+ input {
+ border-radius: 5px;
+ border: none;
+ padding: 4px 4px 4px 10px;
+ margin: 2px;
+ }
+
+ .settings-body {
+ display: flex;
+ flex-direction: row;
+
+
+ .settings-type {
+ display: flex;
+ flex-direction: column;
+ flex-basis: 30%;
+
+ }
+
+ .settings-content {
+ padding-left: 1em;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ text-align: left;
+
+ button {
+ background: $darker-alt-accent;
+ }
+
+ input {
+ min-width: 100%;
+ }
+
+ .error-text {
+ color: #C40233;
+ }
+
+ .success-text {
+ color: #009F6B;
+ }
+ }
+ }
+
+ .focus-span {
+ text-decoration: underline;
+ }
+
+ p {
+ text-align: left;
+ padding: 0;
+ margin: 0 0 20px 0;
+ }
+
+ h1 {
+ color: $dark-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 120%;
+ }
+
+ .close-button {
+ position: absolute;
+ right: 1em;
+ top: 1em;
+ }
+
+ .container {
+ display: block;
+ position: relative;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ font-size: 22px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ width: 700px;
+ min-width: 700px;
+ max-width: 700px;
+ text-align: left;
+ font-style: normal;
+ font-size: 15;
+ font-weight: normal;
+ padding: 0;
+
+ .padding {
+ padding: 0 0 0 20px;
+ color: black;
+ }
+
+
+
+ }
+} \ No newline at end of file
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
new file mode 100644
index 000000000..652af438b
--- /dev/null
+++ b/src/client/util/SettingsManager.tsx
@@ -0,0 +1,124 @@
+import { observable, runInAction, action } from "mobx";
+import * as React from "react";
+import MainViewModal from "../views/MainViewModal";
+import { observer } from "mobx-react";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import * as fa from '@fortawesome/free-solid-svg-icons';
+import { SelectionManager } from "./SelectionManager";
+import "./SettingsManager.scss";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Networking } from "../Network";
+
+library.add(fa.faWindowClose);
+
+@observer
+export default class SettingsManager extends React.Component<{}> {
+ public static Instance: SettingsManager;
+ @observable private isOpen = false;
+ @observable private dialogueBoxOpacity = 1;
+ @observable private overlayOpacity = 0.4;
+ @observable private settingsContent = "password";
+ @observable private errorText = "";
+ @observable private successText = "";
+ private curr_password_ref = React.createRef<HTMLInputElement>();
+ private new_password_ref = React.createRef<HTMLInputElement>();
+ private new_confirm_ref = React.createRef<HTMLInputElement>();
+
+ public open = action(() => {
+ SelectionManager.DeselectAll();
+ this.isOpen = true;
+ });
+
+ public close = action(() => {
+ this.isOpen = false;
+ });
+
+ constructor(props: {}) {
+ super(props);
+ SettingsManager.Instance = this;
+ }
+
+ @action
+ private dispatchRequest = async () => {
+ const curr_pass = this.curr_password_ref.current?.value;
+ const new_pass = this.new_password_ref.current?.value;
+ const new_confirm = this.new_confirm_ref.current?.value;
+
+ if (!(curr_pass && new_pass && new_confirm)) {
+ this.changeAlertText("Hey, we're missing some fields!", "");
+ return;
+ }
+
+ const passwordBundle = {
+ curr_pass,
+ new_pass,
+ new_confirm
+ };
+
+ const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
+ if (error) {
+ this.changeAlertText("Uh oh! " + error[0].msg + "...", "");
+ return;
+ }
+
+ this.changeAlertText("", "Password successfully updated!");
+ }
+
+ @action
+ private changeAlertText = (errortxt: string, successtxt: string) => {
+ this.errorText = errortxt;
+ this.successText = successtxt;
+ }
+
+ @action
+ onClick = (event: any) => {
+ this.settingsContent = event.currentTarget.value;
+ }
+
+ private get settingsInterface() {
+ return (
+ <div className={"settings-interface"}>
+ <div className="settings-heading">
+ <h1>settings</h1>
+ <div className={"close-button"} onClick={this.close}>
+ <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+ </div>
+ </div>
+ <div className="settings-body">
+ <div className="settings-type">
+ <button onClick={this.onClick} value="password">reset password</button>
+ <button onClick={this.onClick} value="data">reset data</button>
+ </div>
+ {this.settingsContent === "password" ?
+ <div className="settings-content">
+ change password here:
+ <input placeholder="current password" ref={this.curr_password_ref} />
+ <input placeholder="new password" ref={this.new_password_ref} />
+ <input placeholder="confirm new password" ref={this.new_confirm_ref} />
+ {this.errorText ? <div className="error-text">{this.errorText}</div> : undefined}
+ {this.successText ? <div className="success-text">{this.successText}</div> : undefined}
+ <button onClick={this.dispatchRequest}>submit</button> <a href="/forgotPassword">forgot password?</a>
+ </div>
+ : undefined}
+ {this.settingsContent === "data" ?
+ <div className="settings-content">hiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii</div>
+ : undefined}
+ </div>
+
+ </div>
+ );
+ }
+
+ render() {
+ return (
+ <MainViewModal
+ contents={this.settingsInterface}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
+ overlayDisplayedOpacity={this.overlayOpacity}
+ />
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index c7fc6096a..ab0a8e49b 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -65,6 +65,18 @@
overflow: hidden;
}
+
+.mainView-settings {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ font-size: 8px;
+}
+
+.mainView-settings:hover {
+ transform: none !important;
+}
+
.mainView-logout {
position: absolute;
right: 5;
@@ -72,6 +84,10 @@
font-size: 8px;
}
+.mainView-logout:hover {
+ transform: none !important;
+}
+
.mainView-libraryFlyout {
height: 100%;
width:100%;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 91c7f909b..05bfee95b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -39,8 +39,12 @@ import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsM
import InkSelectDecorations from './InkSelectDecorations';
import { Scripting } from '../util/Scripting';
import { AudioBox } from './nodes/AudioBox';
+<<<<<<< HEAD
+import SettingsManager from '../util/SettingsManager';
+=======
import { TraceMobx } from '../../new_fields/util';
import RichTextMenu from '../util/RichTextMenu';
+>>>>>>> e410cde0e430553002d4e1a2f64364b57b65fdbc
@observer
export class MainView extends React.Component {
@@ -413,6 +417,9 @@ export class MainView extends React.Component {
zoomToScale={emptyFunction}
getScale={returnOne}>
</DocumentView>
+ <button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}>
+ Settings
+ </button>
<button className="mainView-logout" key="logout" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
{CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
</button>
@@ -509,6 +516,7 @@ export class MainView extends React.Component {
return (<div id="mainView-container">
<DictationOverlay />
<SharingManager />
+ <SettingsManager />
<GoogleAuthenticationManager />
<DocumentDecorations />
<InkSelectDecorations />
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index 88dfa6a64..be452c0ff 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -1,5 +1,5 @@
import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied } from "../RouteManager";
+import { Method, _permission_denied, PublicHandler } from "../RouteManager";
import { WebSocket } from "../Websocket/Websocket";
import { Database } from "../database";
@@ -31,6 +31,21 @@ export default class DeleteManager extends ApiManager {
}
});
+ const hi: PublicHandler = async ({ res, isRelease }) => {
+ if (isRelease) {
+ return _permission_denied(res, deletionPermissionError);
+ }
+ await Database.Instance.deleteAll('users');
+ res.redirect("/home");
+ };
+
+ // register({
+ // method: Method.GET,
+ // subscription: "/deleteUsers",
+ // onValidation: hi,
+ // onUnauthenticated: hi
+ // });
+
register({
method: Method.GET,
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index f2ef22961..36d48e366 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -2,6 +2,8 @@ import ApiManager, { Registration } from "./ApiManager";
import { Method } from "../RouteManager";
import { Database } from "../database";
import { msToTime } from "../ActionUtilities";
+import * as bcrypt from "bcrypt-nodejs";
+import { Opt } from "../../new_fields/Doc";
export const timeMap: { [id: string]: number } = {};
interface ActivityUnit {
@@ -37,6 +39,59 @@ export default class UserManager extends ApiManager {
});
register({
+ method: Method.POST,
+ subscription: '/internalResetPassword',
+ onValidation: async ({ user, req, res }) => {
+ const result: any = {};
+ const { curr_pass, new_pass, new_confirm } = req.body;
+ // perhaps should assert whether curr password is entered correctly
+ const validated = await new Promise<Opt<boolean>>(resolve => {
+ bcrypt.compare(curr_pass, user.password, (err, passwords_match) => {
+ if (err || !passwords_match) {
+ result.error = [{ msg: "Incorrect current password" }];
+ res.send(result);
+ resolve(undefined);
+ } else {
+ resolve(passwords_match);
+ }
+ });
+ });
+
+ if (validated === undefined) {
+ return;
+ }
+
+ req.assert("new_pass", "Password must be at least 4 characters long").len({ min: 4 });
+ req.assert("new_confirm", "Passwords do not match").equals(new_pass);
+ if (curr_pass === new_pass) {
+ result.error = [{ msg: "Current and new password are the same" }];
+ }
+ // was there error in validating new passwords?
+ if (req.validationErrors()) {
+ // was there error?
+ result.error = req.validationErrors();
+ }
+
+ // will only change password if there are no errors.
+ if (!result.error) {
+ user.password = new_pass;
+ user.passwordResetToken = undefined;
+ user.passwordResetExpires = undefined;
+ }
+
+ user.save(err => {
+ if (err) {
+ result.error = [{ msg: "Error while saving new password" }];
+ }
+ });
+
+ res.send(result);
+ }
+ });
+
+
+
+ register({
method: Method.GET,
subscription: "/activity",
secureHandler: ({ res }) => {
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index 5afd607fd..d072b7709 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,6 +1,6 @@
import RouteSubscriber from "./RouteSubscriber";
import { DashUserModel } from "./authentication/models/user_model";
-import * as express from 'express';
+import { Request, Response, Express } from 'express';
import { cyan, red, green } from 'colors';
export enum Method {
@@ -9,8 +9,8 @@ export enum Method {
}
export interface CoreArguments {
- req: express.Request;
- res: express.Response;
+ req: Request;
+ res: Response;
isRelease: boolean;
}
@@ -35,7 +35,7 @@ enum RegistrationError {
}
export default class RouteManager {
- private server: express.Express;
+ private server: Express;
private _isRelease: boolean;
private failedRegistrations: { route: string, reason: RegistrationError }[] = [];
@@ -43,7 +43,7 @@ export default class RouteManager {
return this._isRelease;
}
- constructor(server: express.Express, isRelease: boolean) {
+ constructor(server: Express, isRelease: boolean) {
this.server = server;
this._isRelease = isRelease;
}
@@ -83,9 +83,10 @@ export default class RouteManager {
* @param initializer
*/
addSupervisedRoute = (initializer: RouteInitializer): void => {
- const { method, subscription, secureHandler: onValidation, publicHandler: onUnauthenticated, errorHandler: onError } = initializer;
+ const { method, subscription, secureHandler, publicHandler, errorHandler } = initializer;
+
const isRelease = this._isRelease;
- const supervised = async (req: express.Request, res: express.Response) => {
+ const supervised = async (req: Request, res: Response) => {
let { user } = req;
const { originalUrl: target } = req;
if (process.env.DB === "MEM" && !user) {
@@ -97,19 +98,19 @@ export default class RouteManager {
await toExecute(args);
} catch (e) {
console.log(red(target), user && ("email" in user) ? "<user logged out>" : undefined);
- if (onError) {
- onError({ ...core, error: e });
+ if (errorHandler) {
+ errorHandler({ ...core, error: e });
} else {
_error(res, `The server encountered an internal error when serving ${target}.`, e);
}
}
};
if (user) {
- await tryExecute(onValidation, { ...core, user });
+ await tryExecute(secureHandler, { ...core, user });
} else {
req.session!.target = target;
- if (onUnauthenticated) {
- await tryExecute(onUnauthenticated, core);
+ if (publicHandler) {
+ await tryExecute(publicHandler, core);
if (!res.headersSent) {
res.redirect("/login");
}
@@ -178,22 +179,22 @@ export const STATUS = {
PERMISSION_DENIED: 403
};
-export function _error(res: express.Response, message: string, error?: any) {
+export function _error(res: Response, message: string, error?: any) {
console.error(message);
res.statusMessage = message;
res.status(STATUS.EXECUTION_ERROR).send(error);
}
-export function _success(res: express.Response, body: any) {
+export function _success(res: Response, body: any) {
res.status(STATUS.OK).send(body);
}
-export function _invalid(res: express.Response, message: string) {
+export function _invalid(res: Response, message: string) {
res.statusMessage = message;
res.status(STATUS.BAD_REQUEST).send();
}
-export function _permission_denied(res: express.Response, message?: string) {
+export function _permission_denied(res: Response, message?: string) {
if (message) {
res.statusMessage = message;
}