diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/util/SettingsManager.scss | 120 | ||||
-rw-r--r-- | src/client/util/SettingsManager.tsx | 124 | ||||
-rw-r--r-- | src/client/views/MainView.scss | 16 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 8 | ||||
-rw-r--r-- | src/server/ApiManagers/DeleteManager.ts | 17 | ||||
-rw-r--r-- | src/server/ApiManagers/UserManager.ts | 55 | ||||
-rw-r--r-- | src/server/RouteManager.ts | 33 |
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; } |