From 91c37007a9b2512eadede34ac9656ba2c19d45d6 Mon Sep 17 00:00:00 2001 From: dicedtomatoreal Date: Sun, 4 Oct 2020 21:50:25 -0700 Subject: [PATCH] the tokening --- package-lock.json | 13 +++++ package.json | 1 + src/controllers/UserController.ts | 15 ++++- src/index.ts | 1 - src/lib/Data.ts | 1 + src/lib/Encryption.ts | 8 +-- src/pages/index.tsx | 94 +++++++++++++++++++++++++++++-- src/pages/login.tsx | 30 +++++++++- 8 files changed, 149 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index c90c15cf..8710f18a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2571,6 +2571,14 @@ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, + "copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, "core-js-compat": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", @@ -6836,6 +6844,11 @@ "repeat-string": "^1.6.1" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + }, "toml": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", diff --git a/package.json b/package.json index 678abb74..75d16bf4 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@material-ui/lab": "^4.0.0-alpha.56", "bcrypt": "^5.0.0", "clsx": "^1.1.1", + "copy-to-clipboard": "^3.3.1", "crypto-js": "^4.0.0", "fastify": "^3.5.0", "fastify-cookie": "^4.1.0", diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index 07372acc..ef03d3c2 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -2,7 +2,7 @@ import { FastifyReply, FastifyRequest, FastifyInstance } from 'fastify'; import { Controller, GET, POST, FastifyInstanceToken, Inject, Hook } from 'fastify-decorators'; import { UserNotFoundError, MissingBodyData, LoginError, UserExistsError, NotAdministratorError } from '../lib/api/APIErrors'; import { User } from '../lib/Data'; -import { checkPassword, decrypt, encrypt, encryptPassword } from '../lib/Encryption'; +import { checkPassword, createToken, encryptPassword } from '../lib/Encryption'; @Controller('/api/user') export class UserController { @@ -41,6 +41,18 @@ export class UserController { .send(user); } + @POST('/reset-token') + async resetToken(req: FastifyRequest, reply: FastifyReply) { + if (!req.cookies.zipline) throw new LoginError(`Not logged in.`); + + const users = this.instance.mongo.db.collection('zipline_users'); + const user: User = await users.findOne({ _id: new this.instance.mongo.ObjectId(req.cookies.zipline) }); + if (!user) throw new UserNotFoundError(`User was not found.`); + + users.updateOne({ _id: new this.instance.mongo.ObjectId(req.cookies.zipline) }, { $set: { token: createToken() } }); + return reply.send({ updated: true }); + } + @POST('/create') async create(req: FastifyRequest<{ Body: { username: string, password: string, administrator: boolean } }>, reply: FastifyReply) { if (!req.body.username) throw new MissingBodyData(`Missing username.`); @@ -54,6 +66,7 @@ export class UserController { const newUser: User = { username: req.body.username, password: encryptPassword(req.body.password), + token: createToken(), administrator: req.body.administrator }; diff --git a/src/index.ts b/src/index.ts index e4cc9a49..82d8f6a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,6 @@ import { AddressInfo } from 'net'; import { ConsoleFormatter } from './lib/ConsoleFormatter'; import { bold, green, reset } from '@dicedtomato/colors'; import { Config, Configuration } from './lib/Config'; -import { decrypt, encrypt } from './lib/Encryption'; Console.setFormatter(new ConsoleFormatter()); diff --git a/src/lib/Data.ts b/src/lib/Data.ts index 8ff10dd7..6c277fd9 100644 --- a/src/lib/Data.ts +++ b/src/lib/Data.ts @@ -1,6 +1,7 @@ export interface User { username: string; password?: string; + token?: string; administrator: boolean; _id?: any; } \ No newline at end of file diff --git a/src/lib/Encryption.ts b/src/lib/Encryption.ts index 470d749a..2c8bcab3 100644 --- a/src/lib/Encryption.ts +++ b/src/lib/Encryption.ts @@ -5,12 +5,8 @@ import { Configuration } from './Config'; const config = Configuration.readConfig(); if (!config) process.exit(0); -export function encrypt(data: any) { - return aes.encrypt(JSON.stringify(data), config.core.secret).toString(); -} - -export function decrypt(data: string) { - return aes.decrypt(data, config.core.secret).toString(); +export function createToken() { + return aes.encrypt(Math.random().toString(36).substr(2) + Date.now(), config.core.secret).toString(); } export function encryptPassword(pass: string) { diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 72970f78..52de8efb 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -8,6 +8,14 @@ import TextField from '@material-ui/core/TextField'; import Divider from '@material-ui/core/Divider'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Alert from '@material-ui/lab/Alert'; +import Snackbar from '@material-ui/core/Snackbar'; +import copy from 'copy-to-clipboard'; import { makeStyles } from '@material-ui/core'; import { store } from '../lib/store'; import { useDispatch } from 'react-redux'; @@ -25,24 +33,102 @@ const useStyles = makeStyles({ export default function IndexPage() { const classes = useStyles(); const router = useRouter(); - const dispatch = useDispatch(); const state = store.getState(); + const [alertMessage, setAlertMessage] = useState('Copied token!'); + const [tokenOpen, setTokenOpen] = useState(false); + const [resetToken, setResetToken] = useState(false); + const [alertOpen, setAlertOpen] = useState(false); + + const handleCopyTokenThenClose = async () => { + const data = await (await fetch('/api/user/current')).json(); + if (!data.error) { + copy(data.token); + setAlertMessage('Copied token!'); + setTokenOpen(false); + setAlertOpen(true); + } + }; + + const handleResetTokenThenClose = async () => { + const data = await (await fetch('/api/user/reset-token', { method: 'POST' })).json(); + if (!data.error && data.updated) { + setAlertMessage('Reset token!'); + setResetToken(false); + setAlertOpen(true); + } + }; + if (typeof window !== 'undefined' && !state.loggedIn) router.push('/login'); else { return ( + setAlertOpen(false)} + > + + {alertMessage} + + Welcome back, {state.user.username} You have 2 images
Token - - + setTokenOpen(true)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + Are you sure? + + + This token is used to upload images to Zipline, and should not be shared! + + + + + + + + + setResetToken(true)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + Are you sure? + + + This token is used to upload images to Zipline, resetting your token will cause any uploading actions to not work until you update them your self. + + + + + + +
diff --git a/src/pages/login.tsx b/src/pages/login.tsx index 9709ee01..ece96601 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -5,6 +5,7 @@ import Button from '@material-ui/core/Button'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import CardActions from '@material-ui/core/CardActions'; +import Snackbar from '@material-ui/core/Snackbar'; import Grid from '@material-ui/core/Grid'; import { makeStyles } from '@material-ui/core'; import { useDispatch } from 'react-redux'; @@ -12,7 +13,7 @@ import Router from 'next/router'; import { store, persistor } from '../lib/store'; import { UPDATE_USER, LOGIN } from '../lib/reducer'; import UIPlaceholder from '../components/UIPlaceholder'; - +import Alert from '@material-ui/lab/Alert'; const useStyles = makeStyles({ field: { width: '100%' @@ -28,6 +29,16 @@ export default function Index() { const state = store.getState(); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); + const [open, setOpen] = useState(false); + + const handleClick = () => { + setOpen(true); + }; + + const handleClose = (event, reason) => { + if (reason === 'clickaway') return; + setOpen(false); + }; const handleLogin = async () => { const d = await (await fetch('/api/user/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) })).json() @@ -35,18 +46,33 @@ export default function Index() { dispatch({ type: UPDATE_USER, payload: d }); dispatch({ type: LOGIN }) Router.push('/'); + } else { + setOpen(true); } } if (state.loggedIn) Router.push('/'); else return (
+ + + Could not login! + + - Login + Login setUsername(e.target.value)} /> setPassword(e.target.value)} />